├── .devcontainer ├── Dockerfile ├── devcontainer.json └── post-create.sh ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── chore.md │ ├── documentation-improvement.md │ └── feature-request.md ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── commit-lint-prs.yml │ ├── terra-draw-arcgis-adapter-dry-run-release.yml │ ├── terra-draw-arcgis-adapter-release.yml │ ├── terra-draw-google-maps-adapter-dry-run-release.yml │ ├── terra-draw-google-maps-adapter-release.yml │ ├── terra-draw-leaflet-adapter-dry-run-release.yml │ ├── terra-draw-leaflet-adapter-release.yml │ ├── terra-draw-mapbox-gl-adapter-dry-run-release.yml │ ├── terra-draw-mapbox-gl-adapter-release.yml │ ├── terra-draw-maplibre-gl-adapter-dry-run-release.yml │ ├── terra-draw-maplibre-gl-adapter-release.yml │ ├── terra-draw-openlayers-adapter-dry-run-release.yml │ ├── terra-draw-openlayers-adapter-release.yml │ ├── terra-draw-release-dry-run.yml │ └── terra-draw-release.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmignore ├── .versionrc.js ├── LICENSE ├── README.md ├── assets ├── logo-dark-mode.png ├── logo.png └── readme.gif ├── bump.mjs ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── classes │ ├── terra_draw.TerraDraw.html │ ├── terra_draw.TerraDrawAngledRectangleMode.html │ ├── terra_draw.TerraDrawCircleMode.html │ ├── terra_draw.TerraDrawExtend.GeoJSONStore.html │ ├── terra_draw.TerraDrawExtend.TerraDrawBaseAdapter.html │ ├── terra_draw.TerraDrawExtend.TerraDrawBaseDrawMode.html │ ├── terra_draw.TerraDrawFreehandMode.html │ ├── terra_draw.TerraDrawLineStringMode.html │ ├── terra_draw.TerraDrawPointMode.html │ ├── terra_draw.TerraDrawPolygonMode.html │ ├── terra_draw.TerraDrawRectangleMode.html │ ├── terra_draw.TerraDrawRenderMode.html │ ├── terra_draw.TerraDrawSectorMode.html │ ├── terra_draw.TerraDrawSelectMode.html │ ├── terra_draw.TerraDrawSensorMode.html │ ├── terra_draw_arcgis_adapter.TerraDrawArcGISMapsSDKAdapter.html │ ├── terra_draw_google_maps_adapter.TerraDrawGoogleMapsAdapter.html │ ├── terra_draw_leaflet_adapter.TerraDrawLeafletAdapter.html │ ├── terra_draw_mapbox_gl_adapter.TerraDrawMapboxGLAdapter.html │ ├── terra_draw_maplibre_gl_adapter.TerraDrawMapLibreGLAdapter.html │ └── terra_draw_openlayers_adapter.TerraDrawOpenLayersAdapter.html ├── functions │ ├── terra_draw.ValidateMaxAreaSquareMeters.html │ ├── terra_draw.ValidateMinAreaSquareMeters.html │ └── terra_draw.ValidateNotSelfIntersecting.html ├── hierarchy.html ├── index.html ├── interfaces │ ├── terra_draw.TerraDrawAdapterStyling.html │ ├── terra_draw.TerraDrawChanges.html │ ├── terra_draw.TerraDrawExtend.TerraDrawCallbacks.html │ ├── terra_draw.TerraDrawKeyboardEvent.html │ └── terra_draw.TerraDrawMouseEvent.html ├── media │ ├── 1.GETTING_STARTED.md │ ├── 3.ADAPTERS.md │ ├── 7.DEVELOPMENT.md │ ├── logo.png │ └── readme.gif ├── modules.html ├── modules │ ├── terra_draw.TerraDrawExtend.html │ ├── terra_draw.html │ ├── terra_draw_arcgis_adapter.html │ ├── terra_draw_google_maps_adapter.html │ ├── terra_draw_leaflet_adapter.html │ ├── terra_draw_mapbox_gl_adapter.html │ ├── terra_draw_maplibre_gl_adapter.html │ └── terra_draw_openlayers_adapter.html ├── types │ ├── terra_draw.BehaviorConfig.html │ ├── terra_draw.GeoJSONStoreFeatures.html │ ├── terra_draw.GeoJSONStoreGeometries.html │ ├── terra_draw.GetLngLatFromEvent.html │ ├── terra_draw.HexColor.html │ ├── terra_draw.Project.html │ ├── terra_draw.SetCursor.html │ ├── terra_draw.TerraDrawExtend.BaseAdapterConfig.html │ ├── terra_draw.TerraDrawExtend.BaseModeOptions.html │ ├── terra_draw.TerraDrawExtend.FeatureId.html │ ├── terra_draw.TerraDrawExtend.HexColorStyling.html │ ├── terra_draw.TerraDrawExtend.NumericStyling.html │ ├── terra_draw.TerraDrawStylingFunction.html │ ├── terra_draw.Unproject.html │ └── terra_draw_openlayers_adapter.InjectableOL.html └── variables │ ├── terra_draw.TerraDrawExtend.SELECT_PROPERTIES.html │ └── terra_draw.ValidationReasons.html ├── eslint.config.mjs ├── guides ├── 1.GETTING_STARTED.md ├── 2.STORE.md ├── 3.ADAPTERS.md ├── 4.MODES.md ├── 5.STYLING.md ├── 6.EVENTS.md ├── 7.DEVELOPMENT.md ├── 8.EXAMPLES.md └── CODE_OF_CONDUCT.md ├── jest.config.ts ├── jest.nocheck.config.ts ├── package-lock.json ├── package.json ├── packages ├── development │ ├── .env.example │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── config.ts │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── e2e │ ├── README.md │ ├── package.json │ ├── playwright.config.ts │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ └── index.ts │ ├── tests │ │ ├── leaflet.spec.ts │ │ └── setup.ts │ └── webpack.config.js ├── terra-draw-arcgis-adapter │ ├── .versionrc.cjs │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── jest.nocheck.config.ts │ ├── package.json │ ├── src │ │ ├── terra-draw-arcgis-adapter.spec.ts │ │ └── terra-draw-arcgis-adapter.ts │ └── tsconfig.json ├── terra-draw-google-maps-adapter │ ├── .versionrc.cjs │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── jest.nocheck.config.ts │ ├── package.json │ ├── src │ │ ├── terra-draw-google-maps-adapter.spec.ts │ │ └── terra-draw-google-maps-adapter.ts │ └── tsconfig.json ├── terra-draw-leaflet-adapter │ ├── .versionrc.cjs │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── jest.nocheck.config.ts │ ├── package.json │ ├── src │ │ ├── terra-draw-leaflet-adapter.spec.ts │ │ └── terra-draw-leaflet-adapter.ts │ └── tsconfig.json ├── terra-draw-mapbox-gl-adapter │ ├── .versionrc.cjs │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── jest.nocheck.config.ts │ ├── package.json │ ├── src │ │ ├── terra-draw-mapbox-gl-adapter.spec.ts │ │ └── terra-draw-mapbox-gl-adapter.ts │ └── tsconfig.json ├── terra-draw-maplibre-gl-adapter │ ├── .versionrc.cjs │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── jest.nocheck.config.ts │ ├── package.json │ ├── src │ │ ├── terra-draw-maplibre-gl-adapter.spec.ts │ │ └── terra-draw-maplibre-gl-adapter.ts │ └── tsconfig.json ├── terra-draw-openlayers-adapter │ ├── .versionrc.cjs │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── jest.nocheck.config.ts │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── terra-draw-openlayers-adapter.spec.ts │ │ └── terra-draw-openlayers-adapter.ts │ └── tsconfig.json └── terra-draw │ ├── .versionrc.cjs │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── jest.nocheck.config.ts │ ├── package.json │ ├── src │ ├── common.ts │ ├── common │ │ ├── adapter-listener.spec.ts │ │ ├── adapter-listener.ts │ │ └── base.adapter.ts │ ├── extend.ts │ ├── geometry │ │ ├── boolean │ │ │ ├── is-valid-coordinate.spec.ts │ │ │ ├── is-valid-coordinate.ts │ │ │ ├── point-in-polygon.spec.ts │ │ │ ├── point-in-polygon.ts │ │ │ ├── right-hand-rule.spec.ts │ │ │ ├── right-hand-rule.ts │ │ │ ├── self-intersects.spec.ts │ │ │ └── self-intersects.ts │ │ ├── calculate-relative-angle.spec.ts │ │ ├── calculate-relative-angle.ts │ │ ├── centroid.spec.ts │ │ ├── centroid.ts │ │ ├── clockwise.spec.ts │ │ ├── clockwise.ts │ │ ├── coordinates-identical.spec.ts │ │ ├── coordinates-identical.ts │ │ ├── determine-halfplane.spec.ts │ │ ├── determine-halfplane.ts │ │ ├── ensure-right-hand-rule.spec.ts │ │ ├── ensure-right-hand-rule.ts │ │ ├── get-coordinates-as-points.spec.ts │ │ ├── get-coordinates-as-points.ts │ │ ├── get-midpoint.spec.ts │ │ ├── get-midpoints.ts │ │ ├── helpers.ts │ │ ├── limit-decimal-precision.spec.ts │ │ ├── limit-decimal-precision.ts │ │ ├── measure │ │ │ ├── area.ts │ │ │ ├── bearing.spec.ts │ │ │ ├── bearing.ts │ │ │ ├── desination.spec.ts │ │ │ ├── destination.ts │ │ │ ├── haversine-distance.spec.ts │ │ │ ├── haversine-distance.ts │ │ │ ├── pixel-distance-to-line.spec.ts │ │ │ ├── pixel-distance-to-line.ts │ │ │ ├── pixel-distance.spec.ts │ │ │ ├── pixel-distance.ts │ │ │ ├── rhumb-bearing.spec.ts │ │ │ ├── rhumb-bearing.ts │ │ │ ├── rhumb-destination.spec.ts │ │ │ ├── rhumb-destination.ts │ │ │ ├── rhumb-distance.spec.ts │ │ │ ├── rhumb-distance.ts │ │ │ └── slice-along.ts │ │ ├── midpoint-coordinate.ts │ │ ├── point-on-line.spec.ts │ │ ├── point-on-line.ts │ │ ├── project │ │ │ ├── web-mercator.spec.ts │ │ │ └── web-mercator.ts │ │ ├── shape │ │ │ ├── create-bbox.spec.ts │ │ │ ├── create-bbox.ts │ │ │ ├── create-circle.spec.ts │ │ │ ├── create-circle.ts │ │ │ ├── great-circle-coordinates.spec.ts │ │ │ ├── great-circle-coordinates.ts │ │ │ ├── web-mercator-distortion.spec.ts │ │ │ └── web-mercator-distortion.ts │ │ ├── transform │ │ │ ├── rotate.spec.ts │ │ │ ├── rotate.ts │ │ │ ├── scale.spec.ts │ │ │ └── scale.ts │ │ ├── web-mercator-centroid.spec.ts │ │ ├── web-mercator-centroid.ts │ │ ├── web-mercator-point-on-line.spec.ts │ │ └── web-mercator-point-on-line.ts │ ├── modes │ │ ├── angled-rectangle │ │ │ ├── angled-rectangle.mode.spec.ts │ │ │ └── angled-rectangle.mode.ts │ │ ├── base.behavior.ts │ │ ├── base.mode.ts │ │ ├── circle │ │ │ ├── circle.mode.spec.ts │ │ │ └── circle.mode.ts │ │ ├── click-bounding-box.behavior.spec.ts │ │ ├── click-bounding-box.behavior.ts │ │ ├── coordinate-snapping.behavior.spec.ts │ │ ├── coordinate-snapping.behavior.ts │ │ ├── freehand │ │ │ ├── freehand.mode.spec.ts │ │ │ └── freehand.mode.ts │ │ ├── insert-coordinates.behavior.spec.ts │ │ ├── insert-coordinates.behavior.ts │ │ ├── line-snapping.behavior.spec.ts │ │ ├── line-snapping.behavior.ts │ │ ├── linestring │ │ │ ├── linestring.mode.spec.ts │ │ │ └── linestring.mode.ts │ │ ├── pixel-distance.behavior.spec.ts │ │ ├── pixel-distance.behavior.ts │ │ ├── point │ │ │ ├── point.mode.spec.ts │ │ │ └── point.mode.ts │ │ ├── polygon │ │ │ ├── behaviors │ │ │ │ ├── closing-points.behavior.spec.ts │ │ │ │ └── closing-points.behavior.ts │ │ │ ├── polygon.mode.spec.ts │ │ │ └── polygon.mode.ts │ │ ├── rectangle │ │ │ ├── rectangle.mode.spec.ts │ │ │ └── rectangle.mode.ts │ │ ├── render │ │ │ ├── render.mode.spec.ts │ │ │ └── render.mode.ts │ │ ├── sector │ │ │ ├── sector.mode.spec.ts │ │ │ └── sector.mode.ts │ │ ├── select │ │ │ ├── behaviors │ │ │ │ ├── coordinate-point.behavior.spec.ts │ │ │ │ ├── coordinate-point.behavior.ts │ │ │ │ ├── drag-coordinate-resize.behavior.spec.ts │ │ │ │ ├── drag-coordinate-resize.behavior.ts │ │ │ │ ├── drag-coordinate.behavior.spec.ts │ │ │ │ ├── drag-coordinate.behavior.ts │ │ │ │ ├── drag-feature.behavior.spec.ts │ │ │ │ ├── drag-feature.behavior.ts │ │ │ │ ├── feature-at-pointer-event.behavior.spec.ts │ │ │ │ ├── feature-at-pointer-event.behavior.ts │ │ │ │ ├── midpoint.behavior.spec.ts │ │ │ │ ├── midpoint.behavior.ts │ │ │ │ ├── rotate-feature.behavior.spec.ts │ │ │ │ ├── rotate-feature.behavior.ts │ │ │ │ ├── scale-feature.behavior.spec.ts │ │ │ │ ├── scale-feature.behavior.ts │ │ │ │ ├── selection-point.behavior.spec.ts │ │ │ │ └── selection-point.behavior.ts │ │ │ ├── select.mode.spec.ts │ │ │ └── select.mode.ts │ │ ├── sensor │ │ │ ├── sensor.mode.spec.ts │ │ │ └── sensor.mode.ts │ │ └── static │ │ │ ├── static.mode.spec.ts │ │ │ └── static.mode.ts │ ├── store │ │ ├── spatial-index │ │ │ ├── quickselect.spec.ts │ │ │ ├── quickselect.ts │ │ │ ├── rbush.spec.ts │ │ │ ├── rbush.ts │ │ │ ├── spatial-index.spec.ts │ │ │ └── spatial-index.ts │ │ ├── store-feature-validation.ts │ │ ├── store-validation.spec.ts │ │ ├── store.spec.ts │ │ └── store.ts │ ├── terra-draw.extensions.spec.ts │ ├── terra-draw.spec.ts │ ├── terra-draw.ts │ ├── test │ │ ├── create-store-features.ts │ │ ├── mock-behavior-config.ts │ │ ├── mock-cursor-event.ts │ │ ├── mock-features.ts │ │ ├── mock-keyboard-event.ts │ │ └── mock-mode-config.ts │ ├── util │ │ ├── geom.spec.ts │ │ ├── geoms.ts │ │ ├── id.spec.ts │ │ ├── id.ts │ │ ├── styling.spec.ts │ │ └── styling.ts │ ├── validation-reasons.ts │ └── validations │ │ ├── common-validations.ts │ │ ├── linestring.validation.spec.ts │ │ ├── linestring.validation.ts │ │ ├── max-size.validation.spec.ts │ │ ├── max-size.validation.ts │ │ ├── min-size.validation.spec.ts │ │ ├── min-size.validation.ts │ │ ├── not-self-intersecting.validation.spec.ts │ │ ├── not-self-intersecting.validation.ts │ │ ├── point.validation.spec.ts │ │ ├── point.validation.ts │ │ ├── polygon.validation.spec.ts │ │ └── polygon.validation.ts │ └── tsconfig.json ├── release.js ├── tsconfig.base.json ├── tsconfig.json └── update.mjs /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_MAJOR_VERSION 2 | 3 | FROM mcr.microsoft.com/devcontainers/javascript-node:${NODE_MAJOR_VERSION} 4 | 5 | ENV EDITOR="code -w" VISUAL="code -w" CHOKIDAR_USEPOLLING="1" 6 | 7 | # uncomment to install additional npm packages 8 | # RUN su node -c 'npm i -g cowsay@1.5.0' 9 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw", 3 | "dockerFile": "Dockerfile", 4 | "build": { 5 | "args": { 6 | "NODE_MAJOR_VERSION": "20" 7 | } 8 | }, 9 | "postCreateCommand": [".devcontainer/post-create.sh"], 10 | "portsAttributes": { 11 | "3000": { 12 | "label": "Docs" 13 | }, 14 | "9000": { 15 | "label": "Development" 16 | } 17 | }, 18 | "customizations": { 19 | "vscode": { 20 | "extensions": [ 21 | "dbaeumer.vscode-eslint", 22 | "esbenp.prettier-vscode", 23 | "mikestead.dotenv" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.devcontainer/post-create.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | # when in a VS Code or GitHub Codespaces devcontainer 6 | if [ -n "${REMOTE_CONTAINERS}" ] || [ -n "${CODESPACES}" ]; then 7 | this_dir=$(cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P) 8 | workspace_root=$(realpath ${this_dir}/..) 9 | 10 | # perform additional one-time setup just after 11 | # the devcontainer is created 12 | npm ci --prefix "${workspace_root}" # install lib node dependencies 13 | npm ci --prefix "${workspace_root}/development" # install dev node dependencies 14 | touch "${workspace_root}/development/.env" # ensure dev .env file exists 15 | 16 | fi 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [JamesLMilner] 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve Terra Draw 4 | title: "[Bug]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Terra Draw npm version** 14 | Please tell us exactly which version of Terra Draw you are using 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. iOS] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Smartphone (please complete the following information):** 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/chore.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Chore 3 | about: Technical improvement tasks like updating a dependency etc 4 | title: '[Chore]' 5 | labels: chore 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What technical task needs doing?** 11 | A clear and concise description of what technical task requires doing, such as updating a dependency, fixing a GitHub action, making a change to GitHub repo settings etc 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation improvement 3 | about: Suggest an improvement to the project documentation 4 | title: '[Documentation]' 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What area of the project documentation is missing, lacking or could be clearer?** 11 | Please write clearly and concisely to explain what problems you hit with the documentation 12 | 13 | **How could this documentation be improved? What would you like to see here?** 14 | Suggestions help guide documentation going forward into the future 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Terra Draw 4 | title: "[Feature Request]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe your proposed idea for the solution to this problem** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. For example would this be possible with a custom mode or adapter? 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description of Changes 2 | 3 | 4 | 5 | ## Link to Issue 6 | 7 | 8 | 9 | ## PR Checklist 10 | 11 | - [ ] The PR title follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/#summary) standard 12 | - [ ] There is a associated GitHub issue 13 | - [ ] If I have added significant code changes, there are relevant tests 14 | - [ ] If there are behaviour changes these are documented -------------------------------------------------------------------------------- /.github/workflows/commit-lint-prs.yml: -------------------------------------------------------------------------------- 1 | name: PR Conventional Commit Validation 2 | 3 | permissions: 4 | pull-requests: read 5 | 6 | on: 7 | pull_request: 8 | types: [opened, synchronize, reopened, edited] 9 | 10 | jobs: 11 | validate-pr-title: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Use Node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: "22.x" 19 | - name: Install packages 20 | run: npm ci 21 | - name: PR Conventional Commit Validation Against Commit Lint Config 22 | env: 23 | TITLE: ${{ github.event.pull_request.title }} 24 | run: echo "$TITLE" | npx commitlint -------------------------------------------------------------------------------- /.github/workflows/terra-draw-arcgis-adapter-dry-run-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-arcgis-adapter Dry-run Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-arcgis-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Release 43 | working-directory: ./packages/terra-draw-arcgis-adapter 44 | run: npm run release:dryrun 45 | -------------------------------------------------------------------------------- /.github/workflows/terra-draw-arcgis-adapter-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-arcgis-adapter Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-arcgis-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Release 43 | working-directory: ./packages/terra-draw-arcgis-adapter 44 | run: npm run release 45 | 46 | - name: Update package version references 47 | run: | 48 | node update.mjs 49 | 50 | - name: Check if package-lock.json changed 51 | run: | 52 | npm install 53 | # Check if package-lock.json changed 54 | if ! git diff --exit-code --quiet package-lock.json; then 55 | echo "package-lock.json has changed" 56 | git add \*package.json 57 | git add package-lock.json 58 | git commit -m "chore(terra-draw): automated update to package-lock.json during CI release" 59 | else 60 | echo "No changes to package-lock.json" 61 | fi 62 | 63 | - name: Push upstream 64 | run: git push origin main 65 | 66 | - name: Push tags upstream 67 | run: git push origin main --tags 68 | 69 | - run: npm publish 70 | working-directory: ./packages/terra-draw-arcgis-adapter 71 | env: 72 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/terra-draw-google-maps-adapter-dry-run-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-google-maps-adapter Dry-run Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-google-maps-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Dry Run Release 43 | working-directory: ./packages/terra-draw-google-maps-adapter 44 | run: npm run release:dryrun 45 | -------------------------------------------------------------------------------- /.github/workflows/terra-draw-google-maps-adapter-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-google-maps-adapter Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-google-maps-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Release 43 | working-directory: ./packages/terra-draw-google-maps-adapter 44 | run: npm run release 45 | 46 | - name: Update package version references 47 | run: | 48 | node update.mjs 49 | 50 | - name: Check if package-lock.json changed 51 | run: | 52 | npm install 53 | # Check if package-lock.json changed 54 | if ! git diff --exit-code --quiet package-lock.json; then 55 | echo "package-lock.json has changed" 56 | git add \*package.json 57 | git add package-lock.json 58 | git commit -m "chore(terra-draw): automated update to package-lock.json during CI release" 59 | else 60 | echo "No changes to package-lock.json" 61 | fi 62 | 63 | - name: Push upstream 64 | run: git push origin main 65 | 66 | - name: Push tags upstream 67 | run: git push origin main --tags 68 | 69 | - run: npm publish 70 | working-directory: ./packages/terra-draw-google-maps-adapter 71 | env: 72 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/terra-draw-leaflet-adapter-dry-run-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-leaflet-adapter Dry-run Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-leaflet-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Dry Run Release 43 | working-directory: ./packages/terra-draw-leaflet-adapter 44 | run: npm run release:dryrun 45 | -------------------------------------------------------------------------------- /.github/workflows/terra-draw-leaflet-adapter-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-leaflet-adapter Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-leaflet-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Release 43 | working-directory: ./packages/terra-draw-leaflet-adapter 44 | run: npm run release 45 | 46 | - name: Update package version references 47 | run: | 48 | node update.mjs 49 | 50 | - name: Check if package-lock.json changed 51 | run: | 52 | npm install 53 | # Check if package-lock.json changed 54 | if ! git diff --exit-code --quiet package-lock.json; then 55 | echo "package-lock.json has changed" 56 | git add \*package.json 57 | git add package-lock.json 58 | git commit -m "chore(terra-draw): automated update to package-lock.json during CI release" 59 | else 60 | echo "No changes to package-lock.json" 61 | fi 62 | 63 | - name: Push upstream 64 | run: git push origin main 65 | 66 | - name: Push tags upstream 67 | run: git push origin main --tags 68 | 69 | - run: npm publish 70 | working-directory: ./packages/terra-draw-leaflet-adapter 71 | env: 72 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/terra-draw-mapbox-gl-adapter-dry-run-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-mapbox-gl-adapter Dry-run Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-mapbox-gl-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Dry Run Release 43 | working-directory: ./packages/terra-draw-mapbox-gl-adapter 44 | run: npm run release:dryrun 45 | -------------------------------------------------------------------------------- /.github/workflows/terra-draw-mapbox-gl-adapter-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-mapbox-gl-adapter Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-mapbox-gl-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Release 43 | working-directory: ./packages/terra-draw-mapbox-gl-adapter 44 | run: npm run release 45 | 46 | - name: Update package version references 47 | run: | 48 | node update.mjs 49 | 50 | - name: Check if package-lock.json changed 51 | run: | 52 | npm install 53 | # Check if package-lock.json changed 54 | if ! git diff --exit-code --quiet package-lock.json; then 55 | echo "package-lock.json has changed" 56 | git add \*package.json 57 | git add package-lock.json 58 | git commit -m "chore(terra-draw): automated update to package-lock.json during CI release" 59 | else 60 | echo "No changes to package-lock.json" 61 | fi 62 | 63 | - name: Push upstream 64 | run: git push origin main 65 | 66 | - name: Push tags upstream 67 | run: git push origin main --tags 68 | 69 | - run: npm publish 70 | working-directory: ./packages/terra-draw-mapbox-gl-adapter 71 | env: 72 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/terra-draw-maplibre-gl-adapter-dry-run-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-maplibre-gl-adapter Dry-run Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-maplibre-gl-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Dry Run Release 43 | working-directory: ./packages/terra-draw-maplibre-gl-adapter 44 | run: npm run release:dryrun 45 | -------------------------------------------------------------------------------- /.github/workflows/terra-draw-maplibre-gl-adapter-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-maplibre-gl-adapter Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-maplibre-gl-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Release 43 | working-directory: ./packages/terra-draw-maplibre-gl-adapter 44 | run: npm run release 45 | 46 | - name: Update package version references 47 | run: | 48 | node update.mjs 49 | 50 | - name: Check if package-lock.json changed 51 | run: | 52 | npm install 53 | # Check if package-lock.json changed 54 | if ! git diff --exit-code --quiet package-lock.json; then 55 | echo "package-lock.json has changed" 56 | git add \*package.json 57 | git add package-lock.json 58 | git commit -m "chore(terra-draw): automated update to package-lock.json during CI release" 59 | else 60 | echo "No changes to package-lock.json" 61 | fi 62 | 63 | - name: Push upstream 64 | run: git push origin main 65 | 66 | - name: Push tags upstream 67 | run: git push origin main --tags 68 | 69 | - run: npm publish 70 | working-directory: ./packages/terra-draw-maplibre-gl-adapter 71 | env: 72 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/terra-draw-openlayers-adapter-dry-run-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-openlayers-adapter Dry-run Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-openlayers-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Dry Run Release 43 | working-directory: ./packages/terra-draw-openlayers-adapter 44 | run: npm run release:dryrun 45 | -------------------------------------------------------------------------------- /.github/workflows/terra-draw-openlayers-adapter-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw-openlayers-adapter Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run terra-draw build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Run build 34 | working-directory: ./packages/terra-draw-openlayers-adapter 35 | run: npm run build 36 | 37 | - name: Set git credentials 38 | run: | 39 | git config --global user.email "terradraw@githubactions.com" 40 | git config --global user.name "James Milner" 41 | 42 | - name: Release 43 | working-directory: ./packages/terra-draw-openlayers-adapter 44 | run: npm run release 45 | 46 | - name: Update package version references 47 | run: | 48 | node update.mjs 49 | 50 | - name: Check if package-lock.json changed 51 | run: | 52 | npm install 53 | # Check if package-lock.json changed 54 | if ! git diff --exit-code --quiet package-lock.json; then 55 | echo "package-lock.json has changed" 56 | git add \*package.json 57 | git add package-lock.json 58 | git commit -m "chore(terra-draw): automated update to package-lock.json during CI release" 59 | else 60 | echo "No changes to package-lock.json" 61 | fi 62 | 63 | - name: Push upstream 64 | run: git push origin main 65 | 66 | - name: Push tags upstream 67 | run: git push origin main --tags 68 | 69 | - run: npm publish 70 | working-directory: ./packages/terra-draw-openlayers-adapter 71 | env: 72 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/terra-draw-release-dry-run.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw Dry-run Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Set git credentials 34 | run: | 35 | git config --global user.email "terradraw@githubactions.com" 36 | git config --global user.name "James Milner" 37 | 38 | - name: Dry Run Release 39 | working-directory: ./packages/terra-draw 40 | run: npm run release:dryrun -------------------------------------------------------------------------------- /.github/workflows/terra-draw-release.yml: -------------------------------------------------------------------------------- 1 | name: terra-draw Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: workflow_dispatch 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ secrets.TERRA_DRAW_PAT }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: "22.x" 21 | registry-url: "https://registry.npmjs.org" 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Update Docs 27 | run: npm run docs 28 | 29 | - name: Run build 30 | working-directory: ./packages/terra-draw 31 | run: npm run build 32 | 33 | - name: Set git credentials 34 | run: | 35 | git config --global user.email "terradraw@githubactions.com" 36 | git config --global user.name "James Milner" 37 | 38 | - name: Release 39 | working-directory: ./packages/terra-draw 40 | run: npm run release 41 | 42 | - name: Update package version references 43 | run: | 44 | node update.mjs 45 | 46 | - name: Check if package-lock.json changed 47 | run: | 48 | npm install 49 | # Check if package-lock.json changed 50 | if ! git diff --exit-code --quiet package-lock.json; then 51 | echo "package-lock.json has changed" 52 | git add \*package.json 53 | git add package-lock.json 54 | git commit -m "chore(terra-draw): automated update to package-lock.json during CI release" 55 | else 56 | echo "No changes to package-lock.json" 57 | fi 58 | 59 | - name: Push upstream 60 | run: git push origin main 61 | 62 | - name: Push tags upstream 63 | run: git push origin main --tags 64 | 65 | - run: npm publish 66 | working-directory: ./packages/terra-draw 67 | env: 68 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | coverage/ 3 | dist/ 4 | !docs/dist/ 5 | node_modules/ 6 | example/dist/ 7 | example/node_modules/ 8 | ROADMAP.md 9 | data/ 10 | scratch/ 11 | .idea 12 | .env* 13 | !.env.example 14 | .DS_Store 15 | packages/e2e/test-results/ 16 | packages/e2e/playwright-report/ 17 | packages/e2e/blob-report/ 18 | packages/e2e/playwright/.cache/ 19 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run format:quiet 5 | npm run lint:fix:quiet 6 | git update-index --again -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example/ 2 | .vscode 3 | coverage/ 4 | node_modules/ 5 | src/ 6 | docs/ 7 | development/ 8 | data/ 9 | .husky/ 10 | .github/ 11 | ROADMAP.md 12 | guides/ 13 | readme.gif 14 | jest.config.ts 15 | CHANGELOG.md 16 | scripts/ 17 | scratch/ 18 | assets/ -------------------------------------------------------------------------------- /.versionrc.js: -------------------------------------------------------------------------------- 1 | // List of all of the packages that will want to create a changelog for 2 | const packages = [ 3 | "terra-draw", 4 | "terra-draw-arcgis-adapter", 5 | "terra-draw-google-maps-adapter", 6 | "terra-draw-leaflet-adapter", 7 | "terra-draw-mapbox-gl-adapter", 8 | "terra-draw-maplibre-gl-adapter", 9 | "terra-draw-openlayers-adapter", 10 | ]; 11 | 12 | // The list of the types of commits that will be used to generate the changelog 13 | // See: https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.2.0/README.md#types 14 | const types = []; 15 | 16 | // For each package, add the types of commits that will be used to generate the changelog 17 | packages.forEach((packageName) => { 18 | types.push( 19 | { 20 | type: "feat", 21 | section: "Features", 22 | scope: packageName, 23 | }, 24 | { 25 | type: "fix", 26 | section: "Bug Fixes", 27 | scope: packageName, 28 | }, 29 | { 30 | type: "docs", 31 | section: "Documentation", 32 | scope: packageName, 33 | }, 34 | { 35 | type: "style", 36 | section: "Styling", 37 | scope: packageName, 38 | }, 39 | { 40 | type: "refactor", 41 | section: "Refactors", 42 | scope: packageName, 43 | }, 44 | { 45 | type: "perf", 46 | section: "Performance", 47 | scope: packageName, 48 | }, 49 | { 50 | type: "test", 51 | section: "Tests", 52 | scope: packageName, 53 | }, 54 | { 55 | type: "build", 56 | section: "Build System", 57 | scope: packageName, 58 | }, 59 | { 60 | type: "ci", 61 | section: "CI", 62 | scope: packageName, 63 | }, 64 | { 65 | type: "chore", 66 | section: "Chore", 67 | scope: packageName, 68 | }, 69 | { 70 | type: "revert", 71 | section: "Reverts", 72 | scope: packageName, 73 | }, 74 | ); 75 | }); 76 | 77 | export default { types }; 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 James Milner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /assets/logo-dark-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/terra-draw/1fbcca103b23bfaa38bb3d24829bb9352236789d/assets/logo-dark-mode.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/terra-draw/1fbcca103b23bfaa38bb3d24829bb9352236789d/assets/logo.png -------------------------------------------------------------------------------- /assets/readme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/terra-draw/1fbcca103b23bfaa38bb3d24829bb9352236789d/assets/readme.gif -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-code-background: #FFFFFF; 3 | --dark-code-background: #1E1E1E; 4 | } 5 | 6 | @media (prefers-color-scheme: light) { :root { 7 | --code-background: var(--light-code-background); 8 | } } 9 | 10 | @media (prefers-color-scheme: dark) { :root { 11 | --code-background: var(--dark-code-background); 12 | } } 13 | 14 | :root[data-theme='light'] { 15 | --code-background: var(--light-code-background); 16 | } 17 | 18 | :root[data-theme='dark'] { 19 | --code-background: var(--dark-code-background); 20 | } 21 | 22 | pre, code { background: var(--code-background); } 23 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = 2 | "data:application/octet-stream;base64,H4sIAAAAAAAAE6WXW3PaMBCF/4ufoReapG3eUkIojQkMTvuSyTCLvRg1QnJlkcB08t87BoJvwlrRJ5jhnPP5GFtaPfz1NK61d+lpVArakYIXr+UloBfepbeU0Ypj+n772zT77d1CL7nX8p6YiLzLTssLF4xHCoV3+XCIus/k1wpeemuNImrMq2jL+Wfm/D7KH8HoLtBSYR4eckjT5vCisUz62Pny2qoX+AYpXkWQaFROIFMAGZh9DmXkVs2YQEJ2gfMZhE9pzmNCo5pDSEQeEipPx/lFgVe4E10p5izOcXqTNJNq3grow9fPH887FVh2A0aJZlKkjqiC0wa6QdArhYPIAXHw2MK/47oruVSB3nAmXG5YxWkD3a2WqFjozikbbZig5/e699PxZDTuTe4HvSAnPYNiMLOsDTV/Gfip8/poesBp7xHpXbkSMcdogqGG7Bv9LTUYaS8nU6ELJ9eT4m8U4gJERAcUHSSEzwQGWjER0yFlDwkzlkxoOuEgJ4bzTSyFS/zBQAKc8ES5P0sTFBEqF8KbnhQfYKilQ3yuJ8ZzDB3+4VxPjBep29W/6WkLx273qq2wlp22bGvaXvMVYAEiRvpmvteTsm9xM5Ogot4zCk0mlFwkzlCuUnSD5JbGIQQX8MykfQIpC20bW3Gu3O/uTTOHSe6C6KNcolaMDMkNdoz2ReyDvlFyWfkHDIiqmDrRNIS+SWxRYyV/Y9h0eXuFdSpB3V2ptPGiDhpbWL4+7F7Zm5UIs0GSMlNVLDbUT5FYb8FBYwv7BZxFkGEnCGlpZDaOZjV9bRSrZeMQ1lcKIfizAoVD1KgKlPm+tYlicpZ5F2cmHhOn8upOAu9O6gD5fJAtVCmGurTQN/IMzhqvONrmZ/U2qDBmaRuqh9T6UXu6k07BdBy1neSvVNgfBENI0uD6lnAirsIag+o76JGysZQxx/YSElrjnX6a6U+r3d8GZFdL6WzCHY8it+YIc46a1HivPa2tvzNTqlY5RzLIHZeQzOS6HXNSy516GvPTeg639r5PKVpHHYtx6crZTKFD263+f/r6WQC5cQ13PIrcWiYoOGxQ0V7dXH5a51GCwt/6KZ0NtONJTQP/QGS7bbZdjvzj+7KJV3Sad+vH18d/tIGmga8VAAA="; 3 | -------------------------------------------------------------------------------- /docs/media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/terra-draw/1fbcca103b23bfaa38bb3d24829bb9352236789d/docs/media/logo.png -------------------------------------------------------------------------------- /docs/media/readme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/terra-draw/1fbcca103b23bfaa38bb3d24829bb9352236789d/docs/media/readme.gif -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import tsParser from "@typescript-eslint/parser"; 3 | import eslintPluginPrettier from "eslint-plugin-prettier"; 4 | import prettierConfig from "eslint-config-prettier"; 5 | import json from "@eslint/json"; 6 | import markdown from "@eslint/markdown"; 7 | 8 | const ignores = ["**/node_modules", "**/dist", "**/docs", ".github", ".husky", ".vscode", "**/public", "**/coverage", "packages/e2e/playwright-report"]; 9 | 10 | export default [ 11 | { 12 | ignores 13 | }, 14 | { 15 | files: ["**/*.json"], 16 | language: "json/json", 17 | plugins: { 18 | json, 19 | } 20 | }, 21 | { 22 | files: ["**/*.md"], 23 | plugins: { 24 | markdown 25 | }, 26 | language: "markdown/commonmark", 27 | }, 28 | { 29 | name: "prettier", // Configuration name 30 | files: ["**/*.{js,jsx,ts,tsx,json,md,yml,yaml,html,css}"], 31 | ignores: ['**.json'], 32 | plugins: { 33 | prettier: eslintPluginPrettier, // Include Prettier plugin 34 | }, 35 | rules: { 36 | ...prettierConfig.rules, // Disable ESLint rules that conflict with Prettier 37 | }, 38 | }, 39 | { 40 | name: "typescript", // Configuration name 41 | files: ["**/*.ts"], // TypeScript-specific configuration 42 | plugins: { 43 | "@typescript-eslint": typescriptEslint, // Include TypeScript ESLint plugin 44 | }, 45 | languageOptions: { 46 | parser: tsParser, 47 | }, 48 | rules: { 49 | "@typescript-eslint/no-empty-function": "warn", 50 | "@typescript-eslint/no-explicit-any": "warn", 51 | "no-console": process.env.CI ? "error" : "warn", 52 | }, 53 | }, 54 | ]; 55 | -------------------------------------------------------------------------------- /guides/8.EXAMPLES.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This section provides a list of examples of projects using Terra Draw: 4 | 5 | * [terradraw.io](https://github.com/JamesLMilner/terra-draw-website) - the official Terra Draw website. Uses Terra Draw extensively for all drawing modes. 6 | * [S2 Demo](https://github.com/bdon/s2js-demos) - s2js is a Javascript port of the s2 spherical geometry library. This demo demonstrates drawing using the rectangle and polygon modes and returning the underlying s2 segments. 7 | * [Orama Demo](https://github.com/askorama/examples/tree/main/examples/geosearch-airports) - uses Orama's geosearch capability, where the user can draw a polygon and return the underlying airports in real time. 8 | * [maplibre-gl-terradraw](https://github.com/watergis/maplibre-gl-terradraw) - an easy to use plugin for using Terra Draw with MapLibre. 9 | 10 | If you have your own up to date open source examples, please feel free to raise a PR and add them to the list! 11 | 12 | --- 13 | 14 | **Guides** 15 | 16 | 1. [x] [Getting Started](./1.GETTING_STARTED.md) 17 | 2. [x] [Store](./2.STORE.md) 18 | 3. [x] [Adapters](./3.ADAPTERS.md) 19 | 4. [x] [Modes](./4.MODES.md) 20 | 5. [x] [Styling](./5.STYLING.md) 21 | 6. [x] [Events](./6.EVENTS.md) 22 | 7. [x] [Development](./7.DEVELOPMENT.md) 23 | 8. [x] Examples 24 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log("===== Using ts-jest ======"); 3 | 4 | module.exports = { 5 | preset: "ts-jest", 6 | testEnvironment: "node", 7 | testPathIgnorePatterns: [ 8 | "/node_modules/", 9 | "/packages/e2e/", 10 | "/packages/packages/", 11 | "/docs/", 12 | "/coverage/", 13 | "/scripts/", 14 | "/guides/", 15 | ], 16 | coveragePathIgnorePatterns: [ 17 | "/packages/.*/src/test", 18 | "/packages/e2e/", 19 | "/packages/development/", 20 | ], 21 | collectCoverage: true, 22 | collectCoverageFrom: ["/packages/**/src/**"], 23 | coverageThreshold: { 24 | global: { 25 | lines: 80, 26 | functions: 80, 27 | branches: 80, 28 | statements: 80, 29 | }, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /jest.nocheck.config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log("===== Using @swc/jest ======"); 3 | 4 | module.exports = { 5 | transform: { 6 | "^.+\\.(t|j)sx?$": "@swc/jest", 7 | }, 8 | testPathIgnorePatterns: [ 9 | "/node_modules/", 10 | "/packages/e2e/", 11 | "/packages/packages/", 12 | "/docs/", 13 | "/coverage/", 14 | "/scripts/", 15 | "/guides/", 16 | ], 17 | coveragePathIgnorePatterns: [ 18 | "/packages/.*/src/test", 19 | "/packages/e2e/", 20 | "/packages/development/", 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /packages/development/.env.example: -------------------------------------------------------------------------------- 1 | GOOGLE_API_KEY=YOUR_KEY_HERE 2 | MAPBOX_ACCESS_TOKEN=YOUR_KEY_HERE -------------------------------------------------------------------------------- /packages/development/README.md: -------------------------------------------------------------------------------- 1 | # Local Development Example 2 | 3 | This example shows how to use Terra Draw with a variety of providers (currently Leaflet, Mapbox GL, Google Maps) 4 | 5 | # Installation 6 | 7 | 8 | If you haven't already, you will need to install the dependencies for the root of the project, as well as the dependencies for the `development/` directory: 9 | 10 | 11 | ```shell 12 | npm install 13 | cd development/ 14 | npm install 15 | ``` 16 | 17 | # Running Locally 18 | 19 | Create a `.env` file (or rename the included `.env.example`) in the `development/` directory with the following variables: 20 | 21 | ``` 22 | GOOGLE_API_KEY=YOUR_KEY_HERE 23 | MAPBOX_ACCESS_TOKEN=YOUR_ACCESS_TOKEN_HERE 24 | ``` 25 | 26 | You can then create a watching build that allows you to test out both changes in the example but also the Terra Draw source itself, like so: 27 | 28 | ```shell 29 | npm run serve 30 | ``` 31 | 32 | This will start a hot reloading development server on port 9000 that you can explore via [http://localhost:9000](http://localhost:9000). -------------------------------------------------------------------------------- /packages/development/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw-development", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "An example of how to use Terra Draw with different drawing libraries", 6 | "main": "index.ts", 7 | "scripts": { 8 | "build": "webpack", 9 | "serve": "webpack serve" 10 | }, 11 | "author": "James Milner", 12 | "license": "MIT", 13 | "dependencies": { 14 | "terra-draw": "1.6.3", 15 | "leaflet": "1.9.4", 16 | "maplibre-gl": "3.6.2", 17 | "@arcgis/core": "4.31.6", 18 | "mapbox-gl": "3.9.2", 19 | "ol": "10.3.1", 20 | "google-maps": "4.1.0", 21 | "@googlemaps/js-api-loader": "1.14.3", 22 | "@types/leaflet": "1.9.15" 23 | }, 24 | "devDependencies": { 25 | "dotenv-webpack": "8.0.0", 26 | "typescript": "5.6.3", 27 | "webpack": "5.73.0", 28 | "webpack-cli": "4.10.0", 29 | "webpack-dev-server": "4.11.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/development/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 48 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 |
60 | 61 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
82 |
83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /packages/development/src/config.ts: -------------------------------------------------------------------------------- 1 | export const Libraries = { 2 | Leaflet: "Leaflet", 3 | Mapbox: "Mapbox", 4 | OpenLayers: "OpenLayers", 5 | Google: "Google", 6 | ArcGIS: "ArcGIS", 7 | MapLibre: "MapLibre", 8 | } as const; 9 | 10 | export const Config = { 11 | // You can set this array to just one library or any number and combination 12 | // and the development page will respond accordingly. This is useful if you just want to 13 | // test one map at a time, or you're on a low powered machine. 14 | libraries: [ 15 | Libraries.Leaflet, 16 | Libraries.MapLibre, 17 | Libraries.Mapbox, 18 | Libraries.Google, 19 | Libraries.OpenLayers, 20 | Libraries.ArcGIS, 21 | ] as const, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/development/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../../tsconfig.base.json", 3 | "include": [ 4 | "src", 5 | "../terra-draw/src", 6 | "../terra-draw-google-maps-adapter/src", 7 | "../terra-draw-mapbox-gl-adapter/src", 8 | "../terra-draw-maplibre-gl-adapter/src", 9 | "../terra-draw-leaflet-adapter/src", 10 | "../terra-draw-arcgis-adapter/src", 11 | "../terra-draw-openlayers-adapter/src" 12 | ], 13 | "compilerOptions": { 14 | "composite": true, 15 | "outDir": "dist", 16 | "rootDir": "../" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/development/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Dotenv = require("dotenv-webpack"); 3 | 4 | console.log(path.resolve(__dirname, ".env")); 5 | 6 | module.exports = { 7 | mode: "development", 8 | entry: "./src/index.ts", 9 | devtool: "inline-source-map", 10 | plugins: [ 11 | new Dotenv({ 12 | path: path.resolve(__dirname, ".env"), 13 | }), 14 | ], 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.tsx?$/, 19 | use: "ts-loader", 20 | exclude: /node_modules/, 21 | }, 22 | ], 23 | }, 24 | resolve: { 25 | extensions: [".tsx", ".ts", ".js"], 26 | }, 27 | output: { 28 | filename: "bundle.js", 29 | path: path.resolve(__dirname, "dist"), 30 | }, 31 | devServer: { 32 | static: { 33 | directory: path.join(__dirname, "public"), 34 | }, 35 | watchFiles: [ 36 | "./src", 37 | "./*.{js,json,ts,html}", 38 | "../src", 39 | "../*.{js,json,ts,html}", 40 | ], 41 | compress: true, 42 | port: 9000, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /packages/e2e/README.md: -------------------------------------------------------------------------------- 1 | # E2E Testing with Playwright 2 | 3 | We provide a basic end-to-end (E2E) test suite via Playwright, testing against the Leaflet Adapter. The test suite aims to emulate how an end user would interact with and use Terra Draw. Here we can attempt to catch bugs earlier in the pipeline and ensure that new behaviours in the future adhere to a given specification. 4 | 5 | ## Installation 6 | 7 | Installation can be down via npm like so: 8 | 9 | ```shell 10 | npm install 11 | ``` 12 | 13 | ## Running 14 | 15 | You can run the tests headless (i.e. no opening of Chromimum) like so: 16 | 17 | ```shell 18 | npm run test 19 | ``` 20 | 21 | Or you can run them headed (i.e. Chromimum will open and you will see the tests run) like so: 22 | 23 | ```shell 24 | npm run test:headed 25 | ``` 26 | 27 | ## Tests 28 | 29 | Tests are located in the `tests` folder. You will see the `leaflet.spec.ts` file, this is where the tests are kept for the E2E tests written for the Leaflet Adapter. There are also some convenience methods written in the `setup.ts` file which can be leveraged to write tests more easily. 30 | -------------------------------------------------------------------------------- /packages/e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw-e2e", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "The E2E test suite for Terra Draw", 6 | "main": "index.ts", 7 | "scripts": { 8 | "build": "webpack", 9 | "serve": "webpack serve", 10 | "test": "playwright test --workers=1", 11 | "test:help": "playwright test --help", 12 | "test:ui": "playwright test --ui --headed" 13 | }, 14 | "author": "James Milner", 15 | "license": "MIT", 16 | "dependencies": { 17 | "leaflet": "1.9.4", 18 | "terra-draw": "1.6.3", 19 | "terra-draw-leaflet-adapter": "1.0.3" 20 | }, 21 | "devDependencies": { 22 | "@playwright/test": "^1.49.1", 23 | "@types/jest": "29.5.12", 24 | "@types/node": "20.10.5", 25 | "dotenv-webpack": "8.0.0", 26 | "ts-loader": "9.5.1", 27 | "typescript": "5.6.3", 28 | "webpack": "5.73.0", 29 | "webpack-cli": "4.10.0", 30 | "webpack-dev-server": "4.11.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/e2e/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | 3 | /** 4 | * Read environment variables from file. 5 | * https://github.com/motdotla/dotenv 6 | */ 7 | // require('dotenv').config(); 8 | 9 | /** 10 | * See https://playwright.dev/docs/test-configuration. 11 | */ 12 | export default defineConfig({ 13 | testDir: "./tests", 14 | /* Run tests in files in parallel */ 15 | fullyParallel: true, 16 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 17 | forbidOnly: !!process.env.CI, 18 | /* Retry on CI only */ 19 | retries: process.env.CI ? 2 : 0, 20 | /* Opt out of parallel tests on CI. */ 21 | workers: process.env.CI ? 1 : undefined, 22 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 23 | reporter: "html", 24 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 25 | use: { 26 | /* Base URL to use in actions like `await page.goto('/')`. */ 27 | // baseURL: 'http://127.0.0.1:3000', 28 | 29 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 30 | trace: "on-first-retry", 31 | }, 32 | timeout: 5 * 1000, 33 | /* Configure projects for major browsers */ 34 | projects: [ 35 | { 36 | name: "chromium", 37 | use: { ...devices["Desktop Chrome"] }, 38 | }, 39 | 40 | // { 41 | // name: 'firefox', 42 | // use: { ...devices['Desktop Firefox'] }, 43 | // }, 44 | 45 | // { 46 | // name: 'webkit', 47 | // use: { ...devices['Desktop Safari'] }, 48 | // }, 49 | 50 | /* Test against mobile viewports. */ 51 | // { 52 | // name: 'Mobile Chrome', 53 | // use: { ...devices['Pixel 5'] }, 54 | // }, 55 | // { 56 | // name: 'Mobile Safari', 57 | // use: { ...devices['iPhone 12'] }, 58 | // }, 59 | 60 | /* Test against branded browsers. */ 61 | // { 62 | // name: 'Microsoft Edge', 63 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 64 | // }, 65 | // { 66 | // name: 'Google Chrome', 67 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 68 | // }, 69 | ], 70 | 71 | /* Run your local dev server before starting the tests */ 72 | webServer: { 73 | command: "npm run serve", 74 | url: "http://127.0.0.1:3000", 75 | reuseExistingServer: !process.env.CI, 76 | }, 77 | }); 78 | -------------------------------------------------------------------------------- /packages/e2e/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/terra-draw/1fbcca103b23bfaa38bb3d24829bb9352236789d/packages/e2e/public/favicon.ico -------------------------------------------------------------------------------- /packages/e2e/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 27 | 30 | 31 | 32 | 33 |
34 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /packages/e2e/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Dotenv = require("dotenv-webpack"); 3 | 4 | module.exports = { 5 | mode: "development", 6 | entry: "./src/index.ts", 7 | devtool: "inline-source-map", 8 | plugins: [new Dotenv()], 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | use: "ts-loader", 14 | exclude: /node_modules/, 15 | }, 16 | ], 17 | }, 18 | resolve: { 19 | extensions: [".tsx", ".ts", ".js"], 20 | }, 21 | output: { 22 | filename: "bundle.js", 23 | path: path.resolve(__dirname, "dist"), 24 | }, 25 | devServer: { 26 | static: { 27 | directory: path.join(__dirname, "public"), 28 | }, 29 | liveReload: !process.env.CI, 30 | watchFiles: !process.env.CI 31 | ? ["./src", "./*.{js,json,ts,html}", "../src", "../*.{js,json,ts,html}"] 32 | : [], 33 | compress: true, 34 | port: 3000, 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/terra-draw-arcgis-adapter/.versionrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const packageJsonPath = path.resolve(__dirname, `package.json`); 3 | const packageName = require(packageJsonPath).name 4 | console.log(`✔ Package: ${packageName}`) 5 | 6 | const changelogPath = path.resolve(__dirname, "/CHANGELOG.md") 7 | const releaseConfig = require('../../release') 8 | 9 | module.exports = releaseConfig(packageName, packageJsonPath, changelogPath) 10 | 11 | -------------------------------------------------------------------------------- /packages/terra-draw-arcgis-adapter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. 4 | 5 | ## 1.0.0 (2025-01-12) 6 | 7 | 8 | ### ⚠ BREAKING CHANGE 9 | 10 | * **terra-draw-arcgis-adapter:** breaking change commit to allow adapter to v1.0.0 (#402) 11 | 12 | ### feat 13 | 14 | * **terra-draw-arcgis-adapter:** breaking change commit to allow adapter to v1.0.0 (#402) ([](https://github.com/JamesLMilner/terra-draw/commit/0987990f685387123c95bd52ce6a409ef198e7bb)), closes [#402](https://github.com/JamesLMilner/terra-draw/issues/402) 15 | -------------------------------------------------------------------------------- /packages/terra-draw-arcgis-adapter/README.md: -------------------------------------------------------------------------------- 1 | # TerraDrawArcGISAdapter 2 | 3 | This package is for the Terra Draw ArcGIS Adapter. You can find out more about what adapters are and how to use them in [the adapters guide](./../../guides/3.ADAPTERS.md). For a broader introduction to Terra Draw, check out the main [README](../../README.md) and the [getting started guide](../../guides/1.GETTING_STARTED.md). 4 | 5 | ## License 6 | 7 | [MIT](../../LICENSE) -------------------------------------------------------------------------------- /packages/terra-draw-arcgis-adapter/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = require("../../jest.config"); 2 | 3 | module.exports = { 4 | ...config, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-arcgis-adapter/jest.nocheck.config.ts: -------------------------------------------------------------------------------- 1 | const noCheckConfig = require("../../jest.nocheck.config"); 2 | 3 | module.exports = { 4 | ...noCheckConfig, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-arcgis-adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw-arcgis-adapter", 3 | "version": "1.0.0", 4 | "description": "Terra Draw Adapter for ArcGIS API for JavaScript", 5 | "peerDependencies": { 6 | "terra-draw": "^1.0.0", 7 | "@arcgis/core": "^4.31.6" 8 | }, 9 | "scripts": { 10 | "release": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-arcgis-adapter@ --release-as $TYPE", 11 | "release:dryrun": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-arcgis-adapter@ --dry-run --release-as $TYPE", 12 | "build": "microbundle", 13 | "watch": "microbundle --watch --format modern", 14 | "unused": "knip", 15 | "test": "jest --config jest.config.ts", 16 | "test:coverage": "jest --config jest.config.ts --coverage", 17 | "test:nocheck": "jest --config jest.nocheck.config.ts", 18 | "test:nocheck:coverage": "jest --config jest.nocheck.config.ts --coverage", 19 | "lint": "eslint ", 20 | "lint:quiet": "eslint --quiet ", 21 | "lint:fix": "eslint --fix ", 22 | "lint:fix:quiet": "eslint --fix --quiet ", 23 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", 24 | "format:quiet": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\" --log-level=silent" 25 | }, 26 | "type": "module", 27 | "source": "./src/terra-draw-arcgis-adapter.ts", 28 | "exports": { 29 | "types": "./dist/terra-draw-arcgis-adapter.d.ts", 30 | "require": "./dist/terra-draw-arcgis-adapter.cjs", 31 | "default": "./dist/terra-draw-arcgis-adapter.modern.js" 32 | }, 33 | "types": "./dist/terra-draw-arcgis-adapter.d.ts", 34 | "main": "./dist/terra-draw-arcgis-adapter.cjs", 35 | "module": "./dist/terra-draw-arcgis-adapter.module.js", 36 | "unpkg": "./dist/terra-draw-arcgis-adapter.umd.js", 37 | "author": "James Milner", 38 | "license": "MIT", 39 | "repository": "JamesLMilner/terra-draw", 40 | "keywords": [ 41 | "map", 42 | "drawing", 43 | "draw", 44 | "map drawing", 45 | "geometry", 46 | "arcgis" 47 | ], 48 | "sideEffects": false 49 | } 50 | -------------------------------------------------------------------------------- /packages/terra-draw-arcgis-adapter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src"], 9 | "references": [ 10 | { 11 | "path": "../terra-draw" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/terra-draw-google-maps-adapter/.versionrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const packageJsonPath = path.resolve(__dirname, `package.json`); 3 | const packageName = require(packageJsonPath).name 4 | console.log(`✔ Package: ${packageName}`) 5 | 6 | const changelogPath = path.resolve(__dirname, "/CHANGELOG.md") 7 | const releaseConfig = require('../../release') 8 | 9 | module.exports = releaseConfig(packageName, packageJsonPath, changelogPath) 10 | -------------------------------------------------------------------------------- /packages/terra-draw-google-maps-adapter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. 4 | 5 | ## 1.0.0 (2025-01-12) 6 | 7 | 8 | ### ⚠ BREAKING CHANGE 9 | 10 | * **terra-draw-google-maps-adapter:** breaking change commit to allow adapter to v1.0.0 (#401) 11 | 12 | ### feat 13 | 14 | * **terra-draw-google-maps-adapter:** breaking change commit to allow adapter to v1.0.0 (#401) ([](https://github.com/JamesLMilner/terra-draw/commit/2c182960024d517572882986cb93cf5eb6ced78c)), closes [#401](https://github.com/JamesLMilner/terra-draw/issues/401) 15 | -------------------------------------------------------------------------------- /packages/terra-draw-google-maps-adapter/README.md: -------------------------------------------------------------------------------- 1 | # TerraDrawGoogleMapsAdapter 2 | 3 | This package is for the Terra Draw Google Maps Adapter. You can find out more about what adapters are and how to use them in [the adapters guide](./../../guides/3.ADAPTERS.md). For a broader introduction to Terra Draw, check out the main [README](../../README.md) and the [getting started guide](../../guides/1.GETTING_STARTED.md). 4 | 5 | ## License 6 | 7 | [MIT](../../LICENSE) -------------------------------------------------------------------------------- /packages/terra-draw-google-maps-adapter/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = require("../../jest.config"); 2 | 3 | module.exports = { 4 | ...config, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-google-maps-adapter/jest.nocheck.config.ts: -------------------------------------------------------------------------------- 1 | const noCheckConfig = require("../../jest.nocheck.config"); 2 | 3 | module.exports = { 4 | ...noCheckConfig, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-google-maps-adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw-google-maps-adapter", 3 | "version": "1.0.0", 4 | "description": "Terra Draw Adapter for Google Maps JavaScript API", 5 | "peerDependencies": { 6 | "terra-draw": "^1.0.0", 7 | "@googlemaps/js-api-loader": "^1.14.3" 8 | }, 9 | "devDependencies": { 10 | "@types/google.maps": "^3.49.2" 11 | }, 12 | "scripts": { 13 | "release": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-google-maps-adapter@ --release-as $TYPE", 14 | "release:dryrun": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-google-maps-adapter@ --dry-run --release-as $TYPE", 15 | "build": "microbundle", 16 | "watch": "microbundle --watch --format modern", 17 | "unused": "knip", 18 | "test": "jest --config jest.config.ts", 19 | "test:coverage": "jest --config jest.config.ts --coverage", 20 | "test:nocheck": "jest --config jest.nocheck.config.ts", 21 | "test:nocheck:coverage": "jest --config jest.nocheck.config.ts --coverage", 22 | "lint": "eslint src/", 23 | "lint:quiet": "eslint --quiet src/", 24 | "lint:fix": "eslint --fix src/", 25 | "lint:fix:quiet": "eslint --fix --quiet src/", 26 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", 27 | "format:quiet": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\" --log-level=silent" 28 | }, 29 | "type": "module", 30 | "source": "src/terra-draw-google-maps-adapter.ts", 31 | "exports": { 32 | "types": "./dist/terra-draw-google-maps-adapter.d.ts", 33 | "require": "./dist/terra-draw-google-maps-adapter.cjs", 34 | "default": "./dist/terra-draw-google-maps-adapter.modern.js" 35 | }, 36 | "types": "./dist/terra-draw-google-maps-adapter.d.ts", 37 | "main": "./dist/terra-draw-google-maps-adapter.cjs", 38 | "module": "./dist/terra-draw-google-maps-adapter.module.js", 39 | "unpkg": "./dist/terra-draw-google-maps-adapter.umd.js", 40 | "author": "James Milner", 41 | "license": "MIT", 42 | "repository": "JamesLMilner/terra-draw", 43 | "keywords": [ 44 | "map", 45 | "drawing", 46 | "draw", 47 | "map drawing", 48 | "geometry", 49 | "google maps" 50 | ], 51 | "sideEffects": false 52 | } 53 | -------------------------------------------------------------------------------- /packages/terra-draw-google-maps-adapter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src"], 9 | "references": [ 10 | { 11 | "path": "../terra-draw" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/terra-draw-leaflet-adapter/.versionrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const packageJsonPath = path.resolve(__dirname, `package.json`); 3 | const packageName = require(packageJsonPath).name 4 | console.log(`✔ Package: ${packageName}`) 5 | 6 | const changelogPath = path.resolve(__dirname, "/CHANGELOG.md") 7 | const releaseConfig = require('../../release') 8 | 9 | module.exports = releaseConfig(packageName, packageJsonPath, changelogPath) 10 | -------------------------------------------------------------------------------- /packages/terra-draw-leaflet-adapter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. 4 | 5 | ## [1.0.3](https://github.com/JamesLMilner/terra-draw/compare/terra-draw-leaflet-adapter@1.0.2...terra-draw-leaflet-adapter@1.0.3) (2025-03-13) 6 | 7 | 8 | ### fix 9 | 10 | * **terra-draw-leaflet-adapter:** use polygonOutlineColor instead of polygonFillColor for color (#494) ([](https://github.com/JamesLMilner/terra-draw/commit/37c7e8dd9b27179e30193fa1aff660f893d498f7)), closes [#494](https://github.com/JamesLMilner/terra-draw/issues/494) 11 | 12 | ## [1.0.2](https://github.com/JamesLMilner/terra-draw/compare/terra-draw-leaflet-adapter@1.0.1...terra-draw-leaflet-adapter@1.0.2) (2025-02-26) 13 | 14 | 15 | ### fix 16 | 17 | * **terra-draw-leaflet-adapter:** ensure deleted features are removed from layer state (#482) ([](https://github.com/JamesLMilner/terra-draw/commit/07cc03d40bd91bfd0ebf8fdc5ac1d333f25136cf)), closes [#482](https://github.com/JamesLMilner/terra-draw/issues/482) 18 | 19 | ## [1.0.1](https://github.com/JamesLMilner/terra-draw/compare/terra-draw-leaflet-adapter@1.0.0...terra-draw-leaflet-adapter@1.0.1) (2025-02-25) 20 | 21 | 22 | ### fix 23 | 24 | * **terra-draw-leaflet-adapter:** ensure that the leaflet adapter renders on style change (#480) ([](https://github.com/JamesLMilner/terra-draw/commit/0427855989da84f4ce910c2d9d60d48bfbd3cdb4)), closes [#480](https://github.com/JamesLMilner/terra-draw/issues/480) 25 | 26 | ## 1.0.0 (2025-01-12) 27 | 28 | 29 | ### ⚠ BREAKING CHANGE 30 | 31 | * **terra-draw-leaflet-adapter:** breaking change commit to allow adapter to v1.0.0 (#403) 32 | 33 | ### feat 34 | 35 | * **terra-draw-leaflet-adapter:** breaking change commit to allow adapter to v1.0.0 (#403) ([](https://github.com/JamesLMilner/terra-draw/commit/c1e7946029597426e14e75e91b51d7c437787318)), closes [#403](https://github.com/JamesLMilner/terra-draw/issues/403) 36 | -------------------------------------------------------------------------------- /packages/terra-draw-leaflet-adapter/README.md: -------------------------------------------------------------------------------- 1 | # TerraDrawLeafletAdapter 2 | 3 | This package is for the Terra Draw Leaflet Adapter. You can find out more about what adapters are and how to use them in [the adapters guide](./../../guides/3.ADAPTERS.md). For a broader introduction to Terra Draw, check out the main [README](../../README.md) and the [getting started guide](../../guides/1.GETTING_STARTED.md). 4 | 5 | ## License 6 | 7 | [MIT](../../LICENSE) -------------------------------------------------------------------------------- /packages/terra-draw-leaflet-adapter/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = require("../../jest.config"); 2 | 3 | module.exports = { 4 | ...config, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-leaflet-adapter/jest.nocheck.config.ts: -------------------------------------------------------------------------------- 1 | const noCheckConfig = require("../../jest.nocheck.config"); 2 | 3 | module.exports = { 4 | ...noCheckConfig, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-leaflet-adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw-leaflet-adapter", 3 | "version": "1.0.3", 4 | "description": "Terra Draw Adapter for Leaflet.js", 5 | "peerDependencies": { 6 | "terra-draw": "^1.0.0", 7 | "leaflet": "^1.9.4" 8 | }, 9 | "scripts": { 10 | "release": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-leaflet-adapter@ --release-as $TYPE", 11 | "release:dryrun": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-leaflet-adapter@ --dry-run --release-as $TYPE", 12 | "build": "microbundle", 13 | "watch": "microbundle --watch --format modern", 14 | "unused": "knip", 15 | "test": "jest --config jest.config.ts", 16 | "test:coverage": "jest --config jest.config.ts --coverage", 17 | "test:nocheck": "jest --config jest.nocheck.config.ts", 18 | "test:nocheck:coverage": "jest --config jest.nocheck.config.ts --coverage", 19 | "lint": "eslint src/", 20 | "lint:quiet": "eslint --quiet src/", 21 | "lint:fix": "eslint --fix src/", 22 | "lint:fix:quiet": "eslint --fix --quiet src/", 23 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", 24 | "format:quiet": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\" --log-level=silent" 25 | }, 26 | "type": "module", 27 | "source": "src/terra-draw-leaflet-adapter.ts", 28 | "exports": { 29 | "types": "./dist/terra-draw-leaflet-adapter.d.ts", 30 | "require": "./dist/terra-draw-leaflet-adapter.cjs", 31 | "default": "./dist/terra-draw-leaflet-adapter.modern.js" 32 | }, 33 | "types": "./dist/terra-draw-leaflet-adapter.d.ts", 34 | "main": "./dist/terra-draw-leaflet-adapter.cjs", 35 | "module": "./dist/terra-draw-leaflet-adapter.module.js", 36 | "unpkg": "./dist/terra-draw-leaflet-adapter.umd.js", 37 | "author": "James Milner", 38 | "license": "MIT", 39 | "repository": "JamesLMilner/terra-draw", 40 | "keywords": [ 41 | "map", 42 | "drawing", 43 | "draw", 44 | "map drawing", 45 | "geometry", 46 | "leaflet", 47 | "leafletjs" 48 | ], 49 | "devDependencies": { 50 | "@types/leaflet": "^1.9.15" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/terra-draw-leaflet-adapter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src"], 9 | "references": [ 10 | { 11 | "path": "../terra-draw" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/terra-draw-mapbox-gl-adapter/.versionrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const packageJsonPath = path.resolve(__dirname, `package.json`); 3 | const packageName = require(packageJsonPath).name 4 | console.log(`✔ Package: ${packageName}`) 5 | 6 | const changelogPath = path.resolve(__dirname, "/CHANGELOG.md") 7 | const releaseConfig = require('../../release') 8 | 9 | module.exports = releaseConfig(packageName, packageJsonPath, changelogPath) 10 | -------------------------------------------------------------------------------- /packages/terra-draw-mapbox-gl-adapter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. 4 | 5 | ## [1.1.0](https://github.com/JamesLMilner/terra-draw/compare/terra-draw-mapbox-gl-adapter@1.0.0...terra-draw-mapbox-gl-adapter@1.1.0) (2025-05-21) 6 | 7 | 8 | ### feat 9 | 10 | * **terra-draw-mapbox-gl-adapter:** bring up to parity with changes in maplibre adapter (#554) ([](https://github.com/JamesLMilner/terra-draw/commit/1ee48ec77963e95092deac3bd195d4fe2e6b88eb)), closes [#554](https://github.com/JamesLMilner/terra-draw/issues/554) 11 | 12 | 13 | ### fix 14 | 15 | * **terra-draw-mapbox-gl-adapter:** better handle zindexes to support layered points (#514) ([](https://github.com/JamesLMilner/terra-draw/commit/d26b2fd8f751bae3a6a5a30c228ba5f501103a20)), closes [#514](https://github.com/JamesLMilner/terra-draw/issues/514) 16 | 17 | ## 1.0.0 (2025-01-12) 18 | 19 | 20 | ### ⚠ BREAKING CHANGE 21 | 22 | * **terra-draw-mapbox-gl-adapter:** breaking change commit to allow adapter to v1.0.0 (#404) 23 | 24 | ### feat 25 | 26 | * **terra-draw-mapbox-gl-adapter:** breaking change commit to allow adapter to v1.0.0 (#404) ([](https://github.com/JamesLMilner/terra-draw/commit/b054dd2258632a27a9d595368521a32b27093866)), closes [#404](https://github.com/JamesLMilner/terra-draw/issues/404) 27 | -------------------------------------------------------------------------------- /packages/terra-draw-mapbox-gl-adapter/README.md: -------------------------------------------------------------------------------- 1 | # TerraDrawMapboxGLAdapter 2 | 3 | This package is for the Terra Draw MapboxGL JS Adapter. You can find out more about what adapters are and how to use them in [the adapters guide](./../../guides/3.ADAPTERS.md). For a broader introduction to Terra Draw, check out the main [README](../../README.md) and the [getting started guide](../../guides/1.GETTING_STARTED.md). 4 | 5 | ## License 6 | 7 | [MIT](../../LICENSE) -------------------------------------------------------------------------------- /packages/terra-draw-mapbox-gl-adapter/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = require("../../jest.config"); 2 | 3 | module.exports = { 4 | ...config, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-mapbox-gl-adapter/jest.nocheck.config.ts: -------------------------------------------------------------------------------- 1 | const noCheckConfig = require("../../jest.nocheck.config"); 2 | 3 | module.exports = { 4 | ...noCheckConfig, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-mapbox-gl-adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw-mapbox-gl-adapter", 3 | "version": "1.1.0", 4 | "description": "Terra Draw Adapter for Mapbox GL JS", 5 | "peerDependencies": { 6 | "terra-draw": "^1.0.0", 7 | "mapbox-gl": "^3.9.2" 8 | }, 9 | "scripts": { 10 | "release": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-mapbox-gl-adapter@ --release-as $TYPE", 11 | "release:dryrun": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-mapbox-gl-adapter@ --dry-run --release-as $TYPE", 12 | "build": "microbundle", 13 | "watch": "microbundle --watch --format modern", 14 | "unused": "knip", 15 | "test": "jest --config jest.config.ts", 16 | "test:coverage": "jest --config jest.config.ts --coverage", 17 | "test:nocheck": "jest --config jest.nocheck.config.ts", 18 | "test:nocheck:coverage": "jest --config jest.nocheck.config.ts --coverage", 19 | "lint": "eslint src/", 20 | "lint:quiet": "eslint --quiet src/", 21 | "lint:fix": "eslint --fix src/", 22 | "lint:fix:quiet": "eslint --fix --quiet src/", 23 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", 24 | "format:quiet": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\" --log-level=silent" 25 | }, 26 | "type": "module", 27 | "source": "src/terra-draw-mapbox-gl-adapter.ts", 28 | "exports": { 29 | "source": "./src/terra-draw-mapbox-gl-adapter.ts", 30 | "types": "./dist/terra-draw-mapbox-gl-adapter.d.ts", 31 | "require": "./dist/terra-draw-mapbox-gl-adapter.cjs", 32 | "default": "./dist/terra-draw-mapbox-gl-adapter.modern.js" 33 | }, 34 | "types": "./dist/terra-draw-mapbox-gl-adapter.d.ts", 35 | "main": "./dist/terra-draw-mapbox-gl-adapter.cjs", 36 | "module": "./dist/terra-draw-mapbox-gl-adapter.module.js", 37 | "unpkg": "./dist/terra-draw-mapbox-gl-adapter.umd.js", 38 | "author": "James Milner", 39 | "license": "MIT", 40 | "repository": "JamesLMilner/terra-draw", 41 | "keywords": [ 42 | "map", 43 | "drawing", 44 | "draw", 45 | "map drawing", 46 | "geometry", 47 | "mapbox", 48 | "mapboxgl" 49 | ], 50 | "sideEffects": false 51 | } 52 | -------------------------------------------------------------------------------- /packages/terra-draw-mapbox-gl-adapter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src"], 9 | "references": [ 10 | { 11 | "path": "../terra-draw" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/terra-draw-maplibre-gl-adapter/.versionrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const packageJsonPath = path.resolve(__dirname, `package.json`); 3 | const packageName = require(packageJsonPath).name 4 | console.log(`✔ Package: ${packageName}`) 5 | 6 | const changelogPath = path.resolve(__dirname, "/CHANGELOG.md") 7 | const releaseConfig = require('../../release') 8 | 9 | module.exports = releaseConfig(packageName, packageJsonPath, changelogPath) 10 | -------------------------------------------------------------------------------- /packages/terra-draw-maplibre-gl-adapter/README.md: -------------------------------------------------------------------------------- 1 | # TerraDrawMapLibreGLAdapter 2 | 3 | This package is for the [Terra Draw](https://www.github.com/JamesLMilner/terra-draw) MapLibreGL JS Adapter. The package is designed be used in conjunction with the [`maplibre-gl` package](https://www.npmjs.com/package/maplibre-gl). 4 | 5 | ## Getting Started 6 | 7 | For full getting started instructions for Terra Draw please read the [getting started guide in the GitHub repository](https://github.com/JamesLMilner/terra-draw/blob/main/guides/1.GETTING_STARTED.md). To get started with the MapLibre adapter, please see the detailed [guide on adapters in the GitHub repository](https://github.com/JamesLMilner/terra-draw/blob/main/guides/3.ADAPTERS.md) to understand which are supported and how to get started with them. The guide has a specific section on the [TerraDrawMapLibreGLAdapter](https://github.com/JamesLMilner/terra-draw/blob/main/guides/3.ADAPTERS.md#maplibre). 8 | 9 | 10 | #### Installation 11 | 12 | Installing the core package and this adapter can be done like so: 13 | 14 | ```shell 15 | npm install terra-draw terra-draw-maplibre-gl-adapter 16 | ``` 17 | 18 | #### Quick Links 19 | 20 | * [TerraDrawMapLibreGLAdapter API Docs](https://jameslmilner.github.io/terra-draw/classes/terra_draw_maplibre_gl_adapter.TerraDrawMapLibreGLAdapter.html) 21 | * [GitHub Repository](https://www.github.com/JamesLMilner/terra-draw) 22 | * [Website](https://terradraw.io) 23 | 24 | ## License 25 | 26 | [MIT](https://github.com/JamesLMilner/terra-draw/blob/main/LICENSE) -------------------------------------------------------------------------------- /packages/terra-draw-maplibre-gl-adapter/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = require("../../jest.config"); 2 | 3 | module.exports = { 4 | ...config, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-maplibre-gl-adapter/jest.nocheck.config.ts: -------------------------------------------------------------------------------- 1 | const noCheckConfig = require("../../jest.nocheck.config"); 2 | 3 | module.exports = { 4 | ...noCheckConfig, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-maplibre-gl-adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw-maplibre-gl-adapter", 3 | "version": "1.1.0", 4 | "description": "Terra Draw Adapter for Maplibre GL JS", 5 | "peerDependencies": { 6 | "terra-draw": "^1.0.0", 7 | "maplibre-gl": ">=4" 8 | }, 9 | "scripts": { 10 | "release": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-maplibre-gl-adapter@ --release-as $TYPE", 11 | "release:dryrun": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-maplibre-gl-adapter@ --dry-run --release-as $TYPE", 12 | "build": "microbundle", 13 | "watch": "microbundle --watch --format modern", 14 | "unused": "knip", 15 | "test": "jest --config jest.config.ts", 16 | "test:coverage": "jest --config jest.config.ts --coverage", 17 | "test:nocheck": "jest --config jest.nocheck.config.ts", 18 | "test:nocheck:coverage": "jest --config jest.nocheck.config.ts --coverage", 19 | "lint": "eslint src/", 20 | "lint:quiet": "eslint --quiet src/", 21 | "lint:fix": "eslint --fix src/", 22 | "lint:fix:quiet": "eslint --fix --quiet src/", 23 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", 24 | "format:quiet": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\" --log-level=silent" 25 | }, 26 | "type": "module", 27 | "source": "src/terra-draw-maplibre-gl-adapter.ts", 28 | "exports": { 29 | "types": "./dist/terra-draw-maplibre-gl-adapter.d.ts", 30 | "require": "./dist/terra-draw-maplibre-gl-adapter.cjs", 31 | "default": "./dist/terra-draw-maplibre-gl-adapter.modern.js" 32 | }, 33 | "types": "./dist/terra-draw-maplibre-gl-adapter.d.ts", 34 | "main": "./dist/terra-draw-maplibre-gl-adapter.cjs", 35 | "module": "./dist/terra-draw-maplibre-gl-adapter.module.js", 36 | "unpkg": "./dist/terra-draw-maplibre-gl-adapter.umd.js", 37 | "author": "James Milner", 38 | "license": "MIT", 39 | "repository": "JamesLMilner/terra-draw", 40 | "keywords": [ 41 | "map", 42 | "drawing", 43 | "draw", 44 | "map drawing", 45 | "geometry", 46 | "maplibre", 47 | "maplibre-gl" 48 | ], 49 | "sideEffects": false 50 | } 51 | -------------------------------------------------------------------------------- /packages/terra-draw-maplibre-gl-adapter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src"], 9 | "references": [ 10 | { 11 | "path": "../terra-draw" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/terra-draw-openlayers-adapter/.versionrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const packageJsonPath = path.resolve(__dirname, `package.json`); 3 | const packageName = require(packageJsonPath).name 4 | console.log(`✔ Package: ${packageName}`) 5 | 6 | const changelogPath = path.resolve(__dirname, "/CHANGELOG.md") 7 | const releaseConfig = require('../../release') 8 | 9 | module.exports = releaseConfig(packageName, packageJsonPath, changelogPath) 10 | -------------------------------------------------------------------------------- /packages/terra-draw-openlayers-adapter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. 4 | 5 | ## 1.0.0 (2025-01-12) 6 | 7 | 8 | ### ⚠ BREAKING CHANGE 9 | 10 | * **terra-draw-openlayers-adapter:** breaking change commit to allow adapter to v1.0.0 (#406) 11 | 12 | ### feat 13 | 14 | * **terra-draw-openlayers-adapter:** breaking change commit to allow adapter to v1.0.0 (#406) ([](https://github.com/JamesLMilner/terra-draw/commit/bbbfcd5c45dfe5677301f45d93c15034cbe3ee91)), closes [#406](https://github.com/JamesLMilner/terra-draw/issues/406) 15 | -------------------------------------------------------------------------------- /packages/terra-draw-openlayers-adapter/README.md: -------------------------------------------------------------------------------- 1 | # TerraDrawOpenLayersAdapter 2 | 3 | This package is for the [Terra Draw](https://www.github.com/JamesLMilner/terra-draw) OpenLayers JS Adapter. The package is designed be used in conjunction with the [`ol` package](https://www.npmjs.com/package/ol). 4 | 5 | ## Getting Started 6 | 7 | For full getting started instructions for Terra Draw please read the [getting started guide in the GitHub repository](https://github.com/JamesLMilner/terra-draw/blob/main/guides/1.GETTING_STARTED.md). To get started with the OpenLayers adapter, please see the detailed [guide on adapters in the GitHub repository](https://github.com/JamesLMilner/terra-draw/blob/main/guides/3.ADAPTERS.md) to understand which are supported and how to get started with them. The guide has a specific section on the [TerraDrawOpenLayersAdapter](https://github.com/JamesLMilner/terra-draw/blob/main/guides/3.ADAPTERS.md#openlayers). 8 | 9 | 10 | #### Installation 11 | 12 | Installing the core package and this adapter can be done like so: 13 | 14 | ```shell 15 | npm install terra-draw terra-draw-openlayers-adapter 16 | ``` 17 | 18 | #### Quick Links 19 | 20 | * [TerraDrawOpenLayersAdapter API Docs](https://jameslmilner.github.io/terra-draw/classes/terra_draw_openlayers_adapter.TerraDrawOpenLayersAdapter.html) 21 | * [GitHub Repository](https://www.github.com/JamesLMilner/terra-draw) 22 | * [Website](https://terradraw.io) 23 | 24 | ## License 25 | 26 | [MIT](https://github.com/JamesLMilner/terra-draw/blob/main/LICENSE) -------------------------------------------------------------------------------- /packages/terra-draw-openlayers-adapter/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = require("../../jest.config"); 2 | 3 | module.exports = { 4 | ...config, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-openlayers-adapter/jest.nocheck.config.ts: -------------------------------------------------------------------------------- 1 | const noCheckConfig = require("../../jest.nocheck.config"); 2 | 3 | module.exports = { 4 | ...noCheckConfig, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw-openlayers-adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw-openlayers-adapter", 3 | "version": "1.0.0", 4 | "description": "Terra Draw Adapter to OpenLayers", 5 | "peerDependencies": { 6 | "terra-draw": "^1.0.0", 7 | "ol": "^10.3.1" 8 | }, 9 | "scripts": { 10 | "release": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-openlayers-adapter@ --release-as $TYPE", 11 | "release:dryrun": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw-openlayers-adapter@ --dry-run --release-as $TYPE", 12 | "build": "microbundle", 13 | "watch": "microbundle --watch --format modern", 14 | "unused": "knip", 15 | "test": "jest --config jest.config.ts", 16 | "test:coverage": "jest --config jest.config.ts --coverage", 17 | "test:nocheck": "jest --config jest.nocheck.config.ts", 18 | "test:nocheck:coverage": "jest --config jest.nocheck.config.ts --coverage", 19 | "lint": "eslint src/", 20 | "lint:quiet": "eslint --quiet src/", 21 | "lint:fix": "eslint --fix src/", 22 | "lint:fix:quiet": "eslint --fix --quiet src/", 23 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", 24 | "format:quiet": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\" --log-level=silent" 25 | }, 26 | "type": "module", 27 | "source": "src/terra-draw-openlayers-adapter.ts", 28 | "exports": { 29 | "types": "./dist/terra-draw-openlayers-adapter.d.ts", 30 | "require": "./dist/terra-draw-openlayers-adapter.cjs", 31 | "default": "./dist/terra-draw-openlayers-adapter.modern.js" 32 | }, 33 | "types": "./dist/terra-draw-openlayers-adapter.d.ts", 34 | "main": "./dist/terra-draw-openlayers-adapter.cjs", 35 | "module": "./dist/terra-draw-openlayers-adapter.module.js", 36 | "unpkg": "./dist/terra-draw-openlayers-adapter.umd.js", 37 | "author": "James Milner", 38 | "license": "MIT", 39 | "repository": "JamesLMilner/terra-draw", 40 | "keywords": [ 41 | "map", 42 | "drawing", 43 | "draw", 44 | "map drawing", 45 | "geometry", 46 | "openlayers" 47 | ], 48 | "sideEffects": false 49 | } 50 | -------------------------------------------------------------------------------- /packages/terra-draw-openlayers-adapter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src"], 9 | "references": [ 10 | { 11 | "path": "../terra-draw" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/terra-draw/.versionrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const packageJsonPath = path.resolve(__dirname, `package.json`); 3 | const packageName = require(packageJsonPath).name 4 | console.log(`✔ Package: ${packageName}`) 5 | 6 | const changelogPath = path.resolve(__dirname, "/CHANGELOG.md") 7 | const releaseConfig = require('../../release') 8 | 9 | module.exports = releaseConfig(packageName, packageJsonPath, changelogPath) 10 | -------------------------------------------------------------------------------- /packages/terra-draw/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Terra Draw logo 5 | 6 | 7 |

8 | 9 | ![Terra Draw CI Badge](https://github.com/JamesLMilner/terra-draw/actions/workflows/ci.yml/badge.svg) 10 | [![npm version](https://badge.fury.io/js/terra-draw.svg)](https://badge.fury.io/js/terra-draw) 11 | 12 | Frictionless map drawing across mapping libraries. 13 | 14 | Terra Draw centralizes map drawing logic and provides a host of out-of-the-box drawing modes that work across different JavaScript mapping libraries. It also allows you to bring your own modes! 15 | 16 | ### Supported Mapping Libraries 17 | 18 | Terra Draw works with your mapping library of choice via adapters. Please pick the adapter for the mapping library you are working with: 19 | 20 | * [terra-draw-maplibre-gl-adapter](https://www.npmjs.com/package/terra-draw-maplibre-gl-adapter) 21 | * [terra-draw-leaflet-adapter](https://www.npmjs.com/package/terra-draw-leaflet-adapter) 22 | * [terra-draw-google-maps-adapter](https://www.npmjs.com/package/terra-draw-googl-emaps-adapter) 23 | * [terra-draw-mapbox-gl-adapter](https://www.npmjs.com/package/terra-draw-mapbox-gl-adapter) 24 | * [terra-draw-google-maps-adapter](https://www.npmjs.com/package/terra-draw-google-maps-adapter) 25 | * [terra-draw-arcgis-adapter](https://www.npmjs.com/package/terra-draw-arcgis-adapter) 26 | 27 | ### Getting Started 28 | 29 | * [Getting started guide](https://github.com/JamesLMilner/terra-draw/blob/main/guides/1.GETTING_STARTED.md) 30 | * [API Docs](https://terradraw.io/#/api) 31 | * [Repository](https://www.github.com/JamesLMilner/terra-draw) 32 | * [Website](https://www.terradraw.io) 33 | 34 | ### License 35 | 36 | MIT 37 | -------------------------------------------------------------------------------- /packages/terra-draw/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = require("../../jest.config"); 2 | 3 | module.exports = { 4 | ...config, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw/jest.nocheck.config.ts: -------------------------------------------------------------------------------- 1 | const noCheckConfig = require("../../jest.nocheck.config"); 2 | 3 | module.exports = { 4 | ...noCheckConfig, 5 | testPathIgnorePatterns: ["/dist"], 6 | coveragePathIgnorePatterns: ["/src/test/", "/dist"], 7 | collectCoverageFrom: ["./src/**"], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/terra-draw/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terra-draw", 3 | "version": "1.6.3", 4 | "description": "Frictionless map drawing across mapping provider", 5 | "scripts": { 6 | "release": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw@ --release-as $TYPE", 7 | "release:dryrun": "TYPE=$(node ../../bump.mjs) && commit-and-tag-version .versionrc.cjs -t terra-draw@ --dry-run --release-as $TYPE", 8 | "build": "microbundle", 9 | "watch": "microbundle --watch --format modern", 10 | "unused": "knip", 11 | "test": "jest --config jest.config.ts", 12 | "test:coverage": "jest --config jest.config.ts --coverage", 13 | "test:nocheck": "jest --config jest.nocheck.config.ts", 14 | "test:nocheck:coverage": "jest --config jest.nocheck.config.ts --coverage", 15 | "lint": "eslint src/", 16 | "lint:quiet": "eslint --quiet src/", 17 | "lint:fix": "eslint --fix src/", 18 | "lint:fix:quiet": "eslint --fix --quiet src/", 19 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", 20 | "format:quiet": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\" --log-level=silent" 21 | }, 22 | "type": "module", 23 | "source": "src/terra-draw.ts", 24 | "exports": { 25 | "types": "./dist/terra-draw.d.ts", 26 | "require": "./dist/terra-draw.cjs", 27 | "default": "./dist/terra-draw.modern.js" 28 | }, 29 | "types": "./dist/terra-draw.d.ts", 30 | "main": "./dist/terra-draw.cjs", 31 | "module": "./dist/terra-draw.module.js", 32 | "unpkg": "./dist/terra-draw.umd.js", 33 | "author": "James Milner", 34 | "license": "MIT", 35 | "repository": "JamesLMilner/terra-draw", 36 | "keywords": [ 37 | "map", 38 | "drawing", 39 | "draw", 40 | "map drawing", 41 | "geometry", 42 | "arcgis", 43 | "leaflet", 44 | "maplibre", 45 | "openlayers", 46 | "mapbox" 47 | ], 48 | "sideEffects": false 49 | } 50 | -------------------------------------------------------------------------------- /packages/terra-draw/src/common/adapter-listener.spec.ts: -------------------------------------------------------------------------------- 1 | import { AdapterListener } from "./adapter-listener"; 2 | 3 | describe("AdapterListener", () => { 4 | describe("constructor", () => { 5 | it("constructs with no options", () => { 6 | const callback = jest.fn(); 7 | const listener = new AdapterListener({ 8 | name: "test", 9 | callback, 10 | register: jest.fn(), 11 | unregister: jest.fn(), 12 | }); 13 | expect(listener.name).toBe("test"); 14 | expect(listener.callback).toBe(callback); 15 | }); 16 | }); 17 | 18 | describe("callback", () => { 19 | it("calls the passed callback when called", () => { 20 | const callback = jest.fn(); 21 | const listener = new AdapterListener({ 22 | name: "test", 23 | callback, 24 | register: jest.fn(), 25 | unregister: jest.fn(), 26 | }); 27 | listener.callback(); 28 | 29 | expect(callback).toHaveBeenCalledTimes(1); 30 | }); 31 | }); 32 | 33 | describe("register", () => { 34 | it("calls passed registered function", () => { 35 | const register = jest.fn(); 36 | const callback = jest.fn(); 37 | const listener = new AdapterListener({ 38 | name: "test", 39 | callback, 40 | register, 41 | unregister: jest.fn(), 42 | }); 43 | 44 | listener.register(); 45 | 46 | expect(register).toHaveBeenCalledTimes(1); 47 | expect(register).toHaveBeenCalledWith(callback); 48 | }); 49 | }); 50 | 51 | describe("unregister", () => { 52 | it("calls passed unregister function", () => { 53 | const unregister = jest.fn(); 54 | const callback = jest.fn(); 55 | const listener = new AdapterListener({ 56 | name: "test", 57 | callback, 58 | register: jest.fn(), 59 | unregister, 60 | }); 61 | 62 | listener.unregister(callback); 63 | 64 | expect(unregister).toHaveBeenCalledWith(callback); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/terra-draw/src/common/adapter-listener.ts: -------------------------------------------------------------------------------- 1 | export class AdapterListener any> { 2 | public name: string; 3 | public callback: (...args: any[]) => any; 4 | public registered = false; 5 | public register: any; 6 | public unregister: any; 7 | 8 | /** 9 | * Creates a new AdapterListener instance with the provided configuration. 10 | * 11 | * @param {Object} config - The configuration object for the listener. 12 | * @param {string} config.name - The name of the event listener. 13 | * @param {Function} config.callback - The callback function to be called when the event is triggered. 14 | * @param {Function} config.unregister - The function to unregister the event listeners. 15 | * @param {Function} config.register - The function to register the event listeners. 16 | */ 17 | constructor({ 18 | name, 19 | callback, 20 | unregister, 21 | register, 22 | }: { 23 | name: string; 24 | callback: Callback; 25 | unregister: (callbacks: Callback) => void; 26 | register: (callback: Callback) => void; 27 | }) { 28 | this.name = name; 29 | 30 | // Function to register the event listeners 31 | this.register = () => { 32 | if (!this.registered) { 33 | this.registered = true; 34 | register(callback); 35 | } 36 | }; 37 | 38 | // Function to unregister the event listeners 39 | this.unregister = () => { 40 | if (this.register) { 41 | this.registered = false; 42 | unregister(callback); 43 | } 44 | }; 45 | 46 | this.callback = callback; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/terra-draw/src/extend.ts: -------------------------------------------------------------------------------- 1 | import { TerraDrawBaseAdapter, BaseAdapterConfig } from "./common/base.adapter"; 2 | import { 3 | Cursor, 4 | HexColorStyling, 5 | NumericStyling, 6 | SELECT_PROPERTIES, 7 | TerraDrawCallbacks, 8 | } from "./common"; 9 | import { 10 | BaseModeOptions, 11 | CustomStyling, 12 | TerraDrawBaseDrawMode, 13 | TerraDrawBaseSelectMode, 14 | } from "./modes/base.mode"; 15 | import { FeatureId, GeoJSONStore } from "./store/store"; 16 | import { getDefaultStyling } from "./util/styling"; 17 | 18 | // This object allows 3rd party developers to 19 | // extend these abstract classes and create there 20 | // own modes and adapters 21 | export { 22 | GeoJSONStore, 23 | TerraDrawBaseDrawMode, 24 | TerraDrawBaseSelectMode, 25 | TerraDrawBaseAdapter, 26 | getDefaultStyling, 27 | SELECT_PROPERTIES, 28 | 29 | // Types 30 | FeatureId, 31 | Cursor, 32 | BaseModeOptions, 33 | CustomStyling, 34 | NumericStyling, 35 | HexColorStyling, 36 | TerraDrawCallbacks, 37 | BaseAdapterConfig, 38 | }; 39 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/boolean/is-valid-coordinate.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | 3 | export function validLatitude(lat: number) { 4 | return lat >= -90 && lat <= 90; 5 | } 6 | 7 | export function validLongitude(lng: number) { 8 | return lng >= -180 && lng <= 180; 9 | } 10 | 11 | export function coordinatePrecisionIsValid( 12 | coordinate: Position, 13 | coordinatePrecision: number, 14 | ) { 15 | return ( 16 | getDecimalPlaces(coordinate[0]) <= coordinatePrecision && 17 | getDecimalPlaces(coordinate[1]) <= coordinatePrecision 18 | ); 19 | } 20 | 21 | export function coordinateIsValid(coordinate: unknown[]) { 22 | return ( 23 | coordinate.length === 2 && 24 | typeof coordinate[0] === "number" && 25 | typeof coordinate[1] === "number" && 26 | coordinate[0] !== Infinity && 27 | coordinate[1] !== Infinity && 28 | validLongitude(coordinate[0]) && 29 | validLatitude(coordinate[1]) 30 | ); 31 | } 32 | 33 | export function getDecimalPlaces(value: number): number { 34 | let current = 1; 35 | let precision = 0; 36 | while (Math.round(value * current) / current !== value) { 37 | current *= 10; 38 | precision++; 39 | } 40 | 41 | return precision; 42 | } 43 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/boolean/point-in-polygon.spec.ts: -------------------------------------------------------------------------------- 1 | import { pointInPolygon } from "./point-in-polygon"; 2 | 3 | describe("Geometry", () => { 4 | describe("pointInPolygon", () => { 5 | const polygon = [ 6 | [ 7 | [0, 0], 8 | [0, 100], 9 | [100, 100], 10 | [100, 0], 11 | [0, 0], 12 | ], 13 | ]; 14 | 15 | it("point is not in polygon", () => { 16 | const pointOut = [140, 150]; 17 | expect(pointInPolygon(pointOut, polygon)).toBe(false); 18 | }); 19 | 20 | it("point is in polygon", () => { 21 | const pointIn = [50, 50]; 22 | expect(pointInPolygon(pointIn, polygon)).toBe(true); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/boolean/point-in-polygon.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | 3 | // Based on which-polygon (Mapbox) 4 | // https://github.com/mapbox/which-polygon/blob/2eb5b8a427d018ebd964c05acd3b9166c4558b2c/index.js#L81 5 | // ISC License - Copyright (c) 2017, Mapbox 6 | 7 | export function pointInPolygon(point: Position, rings: Position[][]) { 8 | let inside = false; 9 | for (let i = 0, len = rings.length; i < len; i++) { 10 | const ring = rings[i]; 11 | for (let j = 0, len2 = ring.length, k = len2 - 1; j < len2; k = j++) { 12 | if (rayIntersect(point, ring[j], ring[k])) { 13 | inside = !inside; 14 | } 15 | } 16 | } 17 | return inside; 18 | } 19 | 20 | function rayIntersect(p: Position, p1: Position, p2: Position) { 21 | return ( 22 | p1[1] > p[1] !== p2[1] > p[1] && 23 | p[0] < ((p2[0] - p1[0]) * (p[1] - p1[1])) / (p2[1] - p1[1]) + p1[0] 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/boolean/right-hand-rule.spec.ts: -------------------------------------------------------------------------------- 1 | import { followsRightHandRule } from "./right-hand-rule"; 2 | import { Polygon } from "geojson"; 3 | 4 | describe("followsRightHandRule", () => { 5 | test("returns true for a counterclockwise (right-hand rule) polygon", () => { 6 | const counterclockwisePolygon: Polygon = { 7 | type: "Polygon", 8 | coordinates: [ 9 | [ 10 | [0, 0], 11 | [4, 0], 12 | [4, 4], 13 | [0, 4], 14 | [0, 0], // Counterclockwise order 15 | ], 16 | ], 17 | }; 18 | 19 | expect(followsRightHandRule(counterclockwisePolygon)).toBe(true); 20 | }); 21 | 22 | test("returns false for a clockwise (left-hand rule) polygon", () => { 23 | const clockwisePolygon: Polygon = { 24 | type: "Polygon", 25 | coordinates: [ 26 | [ 27 | [0, 0], 28 | [0, 4], 29 | [4, 4], 30 | [4, 0], 31 | [0, 0], // Clockwise order 32 | ], 33 | ], 34 | }; 35 | 36 | expect(followsRightHandRule(clockwisePolygon)).toBe(false); 37 | }); 38 | 39 | test("returns true for an irregular counterclockwise polygon", () => { 40 | const irregularCCWPolygon: Polygon = { 41 | type: "Polygon", 42 | coordinates: [ 43 | [ 44 | [1, 1], 45 | [3, 0], 46 | [4, 2], 47 | [3, 4], 48 | [1, 3], 49 | [1, 1], // Counterclockwise order 50 | ], 51 | ], 52 | }; 53 | 54 | expect(followsRightHandRule(irregularCCWPolygon)).toBe(true); 55 | }); 56 | 57 | test("returns false for an irregular clockwise polygon", () => { 58 | const irregularCWPolygon: Polygon = { 59 | type: "Polygon", 60 | coordinates: [ 61 | [ 62 | [1, 1], 63 | [1, 3], 64 | [3, 4], 65 | [4, 2], 66 | [3, 0], 67 | [1, 1], // Clockwise order 68 | ], 69 | ], 70 | }; 71 | 72 | expect(followsRightHandRule(irregularCWPolygon)).toBe(false); 73 | }); 74 | 75 | test("returns true for a large counterclockwise polygon", () => { 76 | const largeCCWPolygon: Polygon = { 77 | type: "Polygon", 78 | coordinates: [ 79 | [ 80 | [-10, -10], 81 | [10, -10], 82 | [10, 10], 83 | [-10, 10], 84 | [-10, -10], // Counterclockwise order 85 | ], 86 | ], 87 | }; 88 | 89 | expect(followsRightHandRule(largeCCWPolygon)).toBe(true); 90 | }); 91 | 92 | test("returns false for a small clockwise polygon", () => { 93 | const smallCWPolygon: Polygon = { 94 | type: "Polygon", 95 | coordinates: [ 96 | [ 97 | [2, 2], 98 | [2, 3], 99 | [3, 3], 100 | [3, 2], 101 | [2, 2], // Clockwise order 102 | ], 103 | ], 104 | }; 105 | 106 | expect(followsRightHandRule(smallCWPolygon)).toBe(false); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/boolean/right-hand-rule.ts: -------------------------------------------------------------------------------- 1 | import { Polygon } from "geojson"; 2 | 3 | /** 4 | * Checks if a GeoJSON Polygon follows the right-hand rule. 5 | * @param polygon - The GeoJSON Polygon to check. 6 | * @returns {boolean} - True if the polygon follows the right-hand rule (counterclockwise outer ring), otherwise false. 7 | */ 8 | export function followsRightHandRule(polygon: Polygon): boolean { 9 | const outerRing = polygon.coordinates[0]; 10 | 11 | let sum = 0; 12 | for (let i = 0; i < outerRing.length - 1; i++) { 13 | const [x1, y1] = outerRing[i]; 14 | const [x2, y2] = outerRing[i + 1]; 15 | sum += (x2 - x1) * (y2 + y1); 16 | } 17 | 18 | return sum < 0; // Right-hand rule: counterclockwise = negative area 19 | } 20 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/calculate-relative-angle.spec.ts: -------------------------------------------------------------------------------- 1 | import { calculateRelativeAngle } from "./calculate-relative-angle"; 2 | 3 | describe("calculateRelativeAngle", () => { 4 | it("should return 0 degrees for two collinear lines (same direction)", () => { 5 | const A = { x: 0, y: 0 }; 6 | const B = { x: 1, y: 1 }; 7 | const C = { x: 2, y: 2 }; 8 | 9 | const result = calculateRelativeAngle(A, B, C); 10 | 11 | expect(result).toBe(0); 12 | }); 13 | 14 | it("should return 180 degrees for lines in the opposite direction", () => { 15 | const B = { x: 0, y: 0 }; 16 | const A = { x: 1, y: 1 }; 17 | const C = { x: 2, y: 2 }; 18 | 19 | const result = calculateRelativeAngle(A, B, C); 20 | 21 | expect(result).toBe(180); 22 | }); 23 | 24 | it("should return 90 degrees for lines that are orthogonal (one side)", () => { 25 | const A = { x: 0, y: 0 }; 26 | const B = { x: 0, y: 2 }; 27 | const C = { x: 2, y: 2 }; 28 | 29 | const result = calculateRelativeAngle(A, B, C); 30 | 31 | expect(result).toBe(90); 32 | }); 33 | 34 | it("should return 135 degrees (180 - 45)", () => { 35 | const A = { x: 0, y: 1 }; 36 | const B = { x: 0, y: 0 }; 37 | const C = { x: 1, y: 1 }; 38 | 39 | const result = calculateRelativeAngle(A, B, C); 40 | 41 | expect(result).toBe(135); 42 | }); 43 | 44 | it("should return 45 degrees", () => { 45 | const A = { x: 0, y: 1 }; 46 | const B = { x: 0, y: 0 }; 47 | const C = { x: -1, y: -1 }; 48 | 49 | const result = calculateRelativeAngle(A, B, C); 50 | 51 | expect(result).toBe(45); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/calculate-relative-angle.ts: -------------------------------------------------------------------------------- 1 | import { CartesianPoint } from "../common"; 2 | import { webMercatorBearing } from "./measure/bearing"; 3 | 4 | /** 5 | * Calculate the relative angle between two lines 6 | * @param A The first point of the first line 7 | * @param B The second point of the first line and the first point of the second line 8 | * @param C The second point of the second line 9 | * @returns The relative angle between the two lines 10 | */ 11 | export function calculateRelativeAngle( 12 | A: CartesianPoint, 13 | B: CartesianPoint, 14 | C: CartesianPoint, 15 | ): number { 16 | const bearingAB = webMercatorBearing(A, B); // Bearing from A to B 17 | const bearingBC = webMercatorBearing(B, C); // Bearing from B to C 18 | 19 | // Calculate the relative angle (bearingBC relative to bearingAB) 20 | let relativeAngle = bearingBC - bearingAB; 21 | 22 | // Normalize the relative angle to 0-360 range 23 | if (relativeAngle < 0) { 24 | relativeAngle += 360; 25 | } 26 | 27 | // Normalise to 0 - 90 28 | const angle = relativeAngle - 90; 29 | 30 | return 180 - Math.abs(-90 + angle); 31 | } 32 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/centroid.spec.ts: -------------------------------------------------------------------------------- 1 | import { createLineString, createPolygon } from "../util/geoms"; 2 | import { centroid } from "./centroid"; 3 | 4 | describe("centroid", () => { 5 | it("returns centroid for a given Polygon", () => { 6 | const polygon = createPolygon([ 7 | [ 8 | [0, 0], 9 | [0, 1], 10 | [1, 1], 11 | [1, 0], 12 | [0, 0], 13 | ], 14 | ]); 15 | const result = centroid(polygon); 16 | expect(result).toStrictEqual([0.5, 0.5]); 17 | }); 18 | 19 | it("returns centroid for a given LineString", () => { 20 | const linestring = createLineString([ 21 | [0, 0], 22 | [0, 1], 23 | [1, 1], 24 | [1, 0], 25 | ]); 26 | 27 | const result = centroid(linestring); 28 | expect(result).toStrictEqual([0.5, 0.5]); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/centroid.ts: -------------------------------------------------------------------------------- 1 | import { Feature, LineString, Polygon, Position } from "geojson"; 2 | 3 | // Adapter from the @turf/bearing which is MIT Licensed 4 | // https://github.com/Turfjs/turf/tree/master/packages/turf-centroid 5 | 6 | export function centroid(geojson: Feature): Position { 7 | let xSum = 0; 8 | let ySum = 0; 9 | let len = 0; 10 | 11 | const coordinates = 12 | geojson.geometry.type === "Polygon" 13 | ? geojson.geometry.coordinates[0].slice(0, -1) 14 | : geojson.geometry.coordinates; 15 | 16 | coordinates.forEach((coord: Position) => { 17 | xSum += coord[0]; 18 | ySum += coord[1]; 19 | len++; 20 | }, true); 21 | 22 | return [xSum / len, ySum / len]; 23 | } 24 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/clockwise.spec.ts: -------------------------------------------------------------------------------- 1 | import { isClockwiseWebMercator } from "./clockwise"; 2 | 3 | describe("isClockwiseWebMercator", () => { 4 | it("returns clockwise when coordinates are in clockwise position", () => { 5 | const result = isClockwiseWebMercator( 6 | { x: 1, y: 1 }, 7 | { x: 1, y: 2 }, 8 | { x: 2, y: 1 }, 9 | ); 10 | 11 | // (0, 2) (1, 2) (2, 2) 12 | // (0, 1) (1, 1) (2, 1) 13 | // (0, 0) (1, 0) (2, 0) 14 | expect(result).toBe(true); 15 | }); 16 | 17 | it("returns not clockwise when coordinates are in clockwise position", () => { 18 | const result = isClockwiseWebMercator( 19 | { x: 1, y: 1 }, 20 | { x: 2, y: 1 }, 21 | { x: 1, y: 2 }, 22 | ); 23 | 24 | // (0, 2) (1, 2) (2, 2) 25 | // (0, 1) (1, 1) (2, 1) 26 | // (0, 0) (1, 0) (2, 0) 27 | expect(result).toBe(false); 28 | }); 29 | 30 | it("returns clockwise when coordinates are identical", () => { 31 | const result = isClockwiseWebMercator( 32 | { x: 1, y: 1 }, 33 | { x: 1, y: 2 }, 34 | { x: 1, y: 2 }, 35 | ); 36 | 37 | // (0, 2) (1, 2) (2, 2) 38 | // (0, 1) (1, 1) (2, 1) 39 | // (0, 0) (1, 0) (2, 0) 40 | expect(result).toBe(true); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/clockwise.ts: -------------------------------------------------------------------------------- 1 | import { CartesianPoint } from "../common"; 2 | 3 | export function isClockwiseWebMercator( 4 | center: CartesianPoint, 5 | secondCoord: CartesianPoint, 6 | thirdCoord: CartesianPoint, 7 | ): boolean { 8 | // Calculate the vectors 9 | const vector1 = { x: secondCoord.x - center.x, y: secondCoord.y - center.y }; 10 | const vector2 = { x: thirdCoord.x - center.x, y: thirdCoord.y - center.y }; 11 | 12 | // Calculate the cross product 13 | const cross = vector1.x * vector2.y - vector1.y * vector2.x; 14 | 15 | // If the cross product is negative, the third point is on the right (clockwise) 16 | // If the cross product is positive, the third point is on the left (anticlockwise) 17 | return cross <= 0; 18 | } 19 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/coordinates-identical.spec.ts: -------------------------------------------------------------------------------- 1 | import { coordinatesIdentical } from "./coordinates-identical"; 2 | 3 | describe("coordinatesIdentical", () => { 4 | it("returns false when coordinates not identical", () => { 5 | const result = coordinatesIdentical([0, 0], [1, 1]); 6 | 7 | expect(result).toBe(false); 8 | }); 9 | 10 | it("returns true when coordinates are identical", () => { 11 | const result = coordinatesIdentical([0, 0], [0, 0]); 12 | expect(result).toBe(true); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/coordinates-identical.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | 3 | export function coordinatesIdentical( 4 | coordinate: Position, 5 | coordinateTwo: Position, 6 | ) { 7 | return ( 8 | coordinate[0] === coordinateTwo[0] && coordinate[1] === coordinateTwo[1] 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/determine-halfplane.spec.ts: -------------------------------------------------------------------------------- 1 | import { determineHalfPlane } from "./determine-halfplane"; 2 | 3 | describe("determineHalfPlane", () => { 4 | it('should return "left" when the point is on the left side of the line', () => { 5 | const point = { x: 0, y: 1 }; 6 | const lineStart = { x: -1, y: 0 }; 7 | const lineEnd = { x: 1, y: 0 }; 8 | 9 | const result = determineHalfPlane(point, lineStart, lineEnd); 10 | 11 | expect(result).toBe("left"); 12 | }); 13 | 14 | it('should return "right" when the point is on the right side of the line', () => { 15 | const point = { x: 0, y: -1 }; 16 | const lineStart = { x: -1, y: 0 }; 17 | const lineEnd = { x: 1, y: 0 }; 18 | 19 | const result = determineHalfPlane(point, lineStart, lineEnd); 20 | 21 | expect(result).toBe("right"); 22 | }); 23 | 24 | it('should return "left" when the point is exactly on the line', () => { 25 | const point = { x: 0, y: 0 }; 26 | const lineStart = { x: -1, y: 0 }; 27 | const lineEnd = { x: 1, y: 0 }; 28 | 29 | const result = determineHalfPlane(point, lineStart, lineEnd); 30 | 31 | expect(result).toBe("left"); 32 | }); 33 | 34 | it('should return "left" when the point is at the start of the line', () => { 35 | const point = { x: -1, y: 0 }; 36 | const lineStart = { x: -1, y: 0 }; 37 | const lineEnd = { x: 1, y: 0 }; 38 | 39 | const result = determineHalfPlane(point, lineStart, lineEnd); 40 | 41 | expect(result).toBe("left"); 42 | }); 43 | 44 | it('should return "left" when the point is at the end of the line', () => { 45 | const point = { x: 1, y: 0 }; 46 | const lineStart = { x: -1, y: 0 }; 47 | const lineEnd = { x: 1, y: 0 }; 48 | 49 | const result = determineHalfPlane(point, lineStart, lineEnd); 50 | 51 | expect(result).toBe("left"); 52 | }); 53 | 54 | it("should handle floating-point precision issues", () => { 55 | const point = { x: 0.00000000001, y: 1 }; 56 | const lineStart = { x: -1, y: 0 }; 57 | const lineEnd = { x: 1, y: 0 }; 58 | 59 | const result = determineHalfPlane(point, lineStart, lineEnd); 60 | 61 | expect(result).toBe("left"); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/determine-halfplane.ts: -------------------------------------------------------------------------------- 1 | import { CartesianPoint } from "../common"; 2 | 3 | // Function to determine the relative position of a point to a line segment 4 | export function determineHalfPlane( 5 | point: CartesianPoint, 6 | lineStart: CartesianPoint, 7 | lineEnd: CartesianPoint, 8 | ): string { 9 | // Calculate the vectors 10 | const vectorLine = { x: lineEnd.x - lineStart.x, y: lineEnd.y - lineStart.y }; 11 | const vectorPoint = { x: point.x - lineStart.x, y: point.y - lineStart.y }; 12 | 13 | // Calculate the cross product 14 | const crossProduct = 15 | vectorLine.x * vectorPoint.y - vectorLine.y * vectorPoint.x; 16 | 17 | // Use a small epsilon value to handle floating-point precision errors 18 | const epsilon = 1e-10; 19 | 20 | if (crossProduct > epsilon) { 21 | return "left"; 22 | } else if (crossProduct < -epsilon) { 23 | return "right"; 24 | } else { 25 | // Technically on the line but we treat it as left 26 | return "left"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/ensure-right-hand-rule.spec.ts: -------------------------------------------------------------------------------- 1 | import { Polygon } from "geojson"; 2 | import { followsRightHandRule } from "./boolean/right-hand-rule"; 3 | import { ensureRightHandRule } from "./ensure-right-hand-rule"; 4 | 5 | describe("ensureRightHandRule", () => { 6 | it("should return undefined if the polygon follows the right-hand rule", () => { 7 | const polygon: Polygon = { 8 | type: "Polygon", 9 | coordinates: [ 10 | [ 11 | [0, 0], 12 | [4, 0], 13 | [4, 4], 14 | [0, 4], 15 | [0, 0], 16 | ], 17 | ], 18 | }; 19 | 20 | const result = ensureRightHandRule(polygon); 21 | expect(result).toBeUndefined(); 22 | }); 23 | 24 | it("should return a reversed polygon if it does not follow the right-hand rule", () => { 25 | const polygon: Polygon = { 26 | type: "Polygon", 27 | coordinates: [ 28 | [ 29 | [0, 0], 30 | [0, 4], 31 | [4, 4], 32 | [4, 0], 33 | [0, 0], 34 | ], 35 | ], 36 | }; 37 | 38 | const result = ensureRightHandRule(polygon); 39 | 40 | expect(result).toEqual({ 41 | type: "Polygon", 42 | coordinates: [ 43 | [ 44 | [0, 0], 45 | [4, 0], 46 | [4, 4], 47 | [0, 4], 48 | [0, 0], 49 | ], 50 | ], 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/ensure-right-hand-rule.ts: -------------------------------------------------------------------------------- 1 | import { Feature, Polygon } from "geojson"; 2 | import { followsRightHandRule } from "./boolean/right-hand-rule"; 3 | 4 | export function ensureRightHandRule(polygon: Polygon): undefined | Polygon { 5 | const isFollowingRightHandRule = followsRightHandRule(polygon); 6 | if (!isFollowingRightHandRule) { 7 | return { 8 | type: "Polygon", 9 | coordinates: [polygon.coordinates[0].reverse()], 10 | } as Polygon; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/get-coordinates-as-points.spec.ts: -------------------------------------------------------------------------------- 1 | import { getCoordinatesAsPoints } from "./get-coordinates-as-points"; 2 | 3 | describe("getCoordinatesAsPoints", () => { 4 | it("returns coordinates as Points for a given Polygon", () => { 5 | const result = getCoordinatesAsPoints( 6 | [ 7 | [0, 0], 8 | [0, 1], 9 | [1, 1], 10 | [1, 0], 11 | [0, 0], 12 | ], 13 | "Polygon", 14 | () => { 15 | return {}; 16 | }, 17 | ); 18 | expect(result.length).toStrictEqual(4); 19 | result.forEach((point) => { 20 | expect(point.geometry.type).toBe("Point"); 21 | }); 22 | }); 23 | 24 | it("returns coordinates as Points for a given LineString", () => { 25 | const result = getCoordinatesAsPoints( 26 | [ 27 | [0, 0], 28 | [0, 1], 29 | [1, 1], 30 | [1, 0], 31 | ], 32 | "LineString", 33 | () => { 34 | return {}; 35 | }, 36 | ); 37 | expect(result.length).toStrictEqual(4); 38 | result.forEach((point) => { 39 | expect(point.geometry.type).toBe("Point"); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/get-coordinates-as-points.ts: -------------------------------------------------------------------------------- 1 | import { Point, Position } from "geojson"; 2 | import { JSONObject } from "../store/store"; 3 | 4 | export function getCoordinatesAsPoints( 5 | selectedCoords: Position[], 6 | geometryType: "Polygon" | "LineString", 7 | properties: (index: number) => JSONObject, 8 | ) { 9 | const selectionPoints = []; 10 | 11 | // We can skip the last point for polygons 12 | // as it's a duplicate of the first 13 | const length = 14 | geometryType === "Polygon" 15 | ? selectedCoords.length - 1 16 | : selectedCoords.length; 17 | 18 | for (let i = 0; i < length; i++) { 19 | selectionPoints.push({ 20 | geometry: { 21 | type: "Point", 22 | coordinates: selectedCoords[i], 23 | } as Point, 24 | properties: properties(i), 25 | }); 26 | } 27 | 28 | return selectionPoints; 29 | } 30 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/get-midpoint.spec.ts: -------------------------------------------------------------------------------- 1 | import { getMidPointCoordinates } from "./get-midpoints"; 2 | 3 | describe("getMidPointCoordinates", () => { 4 | it("get midpoint coordinates", () => { 5 | const result = getMidPointCoordinates({ 6 | featureCoords: [ 7 | [0, 0], 8 | [0, 1], 9 | [1, 1], 10 | [0, 1], 11 | [0, 0], 12 | ], 13 | precision: 9, 14 | project: (lng: number, lat: number) => { 15 | return { x: lng * 100, y: lat * 100 }; 16 | }, 17 | unproject: (x: number, y: number) => { 18 | return { lng: x / 100, lat: y / 100 }; 19 | }, 20 | projection: "web-mercator", 21 | }); 22 | 23 | expect(result).toStrictEqual([ 24 | [0, 0.5], 25 | [0.5, 1], 26 | [0.5, 1], 27 | [0, 0.5], 28 | ]); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/get-midpoints.ts: -------------------------------------------------------------------------------- 1 | import { Point, Position } from "geojson"; 2 | import { Project, Projection, Unproject } from "../common"; 3 | import { JSONObject } from "../store/store"; 4 | import { 5 | midpointCoordinate, 6 | geodesicMidpointCoordinate, 7 | } from "./midpoint-coordinate"; 8 | 9 | export function getMidPointCoordinates({ 10 | featureCoords, 11 | precision, 12 | unproject, 13 | project, 14 | projection, 15 | }: { 16 | featureCoords: Position[]; 17 | precision: number; 18 | project: Project; 19 | unproject: Unproject; 20 | projection: Projection; 21 | }) { 22 | const midPointCoords: Position[] = []; 23 | for (let i = 0; i < featureCoords.length - 1; i++) { 24 | let mid; 25 | if (projection === "web-mercator") { 26 | mid = midpointCoordinate( 27 | featureCoords[i], 28 | featureCoords[i + 1], 29 | precision, 30 | project, 31 | unproject, 32 | ); 33 | } else if (projection === "globe") { 34 | mid = geodesicMidpointCoordinate( 35 | featureCoords[i], 36 | featureCoords[i + 1], 37 | precision, 38 | ); 39 | } else { 40 | throw new Error("Invalid projection"); 41 | } 42 | 43 | midPointCoords.push(mid); 44 | } 45 | return midPointCoords; 46 | } 47 | 48 | export function getMidPoints( 49 | selectedCoords: Position[], 50 | properties: (index: number) => JSONObject, 51 | precision: number, 52 | project: Project, 53 | unproject: Unproject, 54 | projection: Projection, 55 | ) { 56 | return getMidPointCoordinates({ 57 | featureCoords: selectedCoords, 58 | precision, 59 | project, 60 | unproject, 61 | projection, 62 | }).map((coord, i) => ({ 63 | geometry: { type: "Point", coordinates: coord } as Point, 64 | properties: properties(i), 65 | })); 66 | } 67 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/helpers.ts: -------------------------------------------------------------------------------- 1 | export const earthRadius = 6371008.8; 2 | 3 | export function degreesToRadians(degrees: number): number { 4 | const radians = degrees % 360; 5 | return (radians * Math.PI) / 180; 6 | } 7 | 8 | export function lengthToRadians(distance: number): number { 9 | const factor = earthRadius / 1000; 10 | return distance / factor; 11 | } 12 | 13 | export function radiansToDegrees(radians: number): number { 14 | const degrees = radians % (2 * Math.PI); 15 | return (degrees * 180) / Math.PI; 16 | } 17 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/limit-decimal-precision.spec.ts: -------------------------------------------------------------------------------- 1 | import { limitPrecision } from "./limit-decimal-precision"; 2 | 3 | describe("limitPrecision", () => { 4 | it("returns decimal at a given precision", () => { 5 | const result = limitPrecision(0.11111111111111); 6 | expect(result).toBe(0.111111111); 7 | }); 8 | 9 | it("can pass a numeric decimal precision limiter", () => { 10 | const result = limitPrecision(0.11111111111111, 3); 11 | expect(result).toBe(0.111); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/limit-decimal-precision.ts: -------------------------------------------------------------------------------- 1 | export function limitPrecision(num: number, decimalLimit = 9) { 2 | const decimals = Math.pow(10, decimalLimit); 3 | return Math.round(num * decimals) / decimals; 4 | } 5 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/area.ts: -------------------------------------------------------------------------------- 1 | import { Polygon } from "geojson"; 2 | import { earthRadius } from "../helpers"; 3 | 4 | // Adapted from @turf/area is MIT Licensed licensed https://github.com/Turfjs/turf/blob/master/packages/turf-area/index.ts 5 | // In turn adapted from NASA: https://dataverse.jpl.nasa.gov/file.xhtml?fileId=47998&version=2.0 6 | 7 | export function polygonAreaSquareMeters(polygon: Polygon) { 8 | const coords = polygon.coordinates; 9 | let total = 0; 10 | if (coords && coords.length > 0) { 11 | total += Math.abs(ringArea(coords[0])); 12 | for (let i = 1; i < coords.length; i++) { 13 | total -= Math.abs(ringArea(coords[i])); 14 | } 15 | } 16 | return total; 17 | } 18 | 19 | const FACTOR = (earthRadius * earthRadius) / 2; 20 | const PI_OVER_180 = Math.PI / 180; 21 | 22 | function ringArea(coords: number[][]): number { 23 | const coordsLength = coords.length; 24 | 25 | if (coordsLength <= 2) { 26 | return 0; 27 | } 28 | 29 | let total = 0; 30 | 31 | let i = 0; 32 | while (i < coordsLength) { 33 | const lower = coords[i]; 34 | const middle = coords[i + 1 === coordsLength ? 0 : i + 1]; 35 | const upper = 36 | coords[i + 2 >= coordsLength ? (i + 2) % coordsLength : i + 2]; 37 | 38 | const lowerX = lower[0] * PI_OVER_180; 39 | const middleY = middle[1] * PI_OVER_180; 40 | const upperX = upper[0] * PI_OVER_180; 41 | 42 | total += (upperX - lowerX) * Math.sin(middleY); 43 | 44 | i++; 45 | } 46 | 47 | return total * FACTOR; 48 | } 49 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/bearing.spec.ts: -------------------------------------------------------------------------------- 1 | import { bearing, webMercatorBearing } from "./bearing"; 2 | 3 | describe("bearing", () => { 4 | it("bearing between two identical points is zero", () => { 5 | expect(bearing([0, 0], [0, 0])).toBe(0); 6 | }); 7 | 8 | it("bearing should return positive value", () => { 9 | expect(bearing([0, 0], [1, 1])).toBe(44.99563645534486); 10 | }); 11 | }); 12 | 13 | describe("webMercatorBearing", () => { 14 | it("bearing between two identical points is zero", () => { 15 | expect(webMercatorBearing({ x: 0, y: 0 }, { x: 0, y: 0 })).toBe(0); 16 | }); 17 | 18 | it("bearing should return positive value", () => { 19 | expect(webMercatorBearing({ x: 0, y: 0 }, { x: 1, y: 1 })).toBe(45); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/bearing.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | import { degreesToRadians, radiansToDegrees } from "../helpers"; 3 | import { CartesianPoint } from "../../common"; 4 | 5 | // Adapted from the @turf/bearing module which is MIT Licensed 6 | // https://github.com/Turfjs/turf/tree/master/packages/turf-bearing 7 | 8 | export function bearing(start: Position, end: Position): number { 9 | const lon1 = degreesToRadians(start[0]); 10 | const lon2 = degreesToRadians(end[0]); 11 | const lat1 = degreesToRadians(start[1]); 12 | const lat2 = degreesToRadians(end[1]); 13 | const a = Math.sin(lon2 - lon1) * Math.cos(lat2); 14 | const b = 15 | Math.cos(lat1) * Math.sin(lat2) - 16 | Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1); 17 | 18 | return radiansToDegrees(Math.atan2(a, b)); 19 | } 20 | 21 | export function webMercatorBearing( 22 | { x: x1, y: y1 }: CartesianPoint, 23 | { x: x2, y: y2 }: CartesianPoint, 24 | ): number { 25 | const deltaX = x2 - x1; 26 | const deltaY = y2 - y1; 27 | 28 | if (deltaX === 0 && deltaY === 0) { 29 | return 0; // No movement 30 | } 31 | 32 | // Calculate the angle in radians 33 | let angle = Math.atan2(deltaY, deltaX); 34 | 35 | // Convert the angle to degrees 36 | angle = angle * (180 / Math.PI); 37 | 38 | // Normalize to -180 to 180 39 | if (angle > 180) { 40 | angle -= 360; 41 | } else if (angle < -180) { 42 | angle += 360; 43 | } 44 | 45 | return angle; 46 | } 47 | 48 | export function normalizeBearing(bearing: number): number { 49 | return (bearing + 360) % 360; 50 | } 51 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/desination.spec.ts: -------------------------------------------------------------------------------- 1 | import { destination } from "./destination"; 2 | 3 | describe("destination", () => { 4 | it("bearing between two identical points is zero", () => { 5 | expect(destination([0, 0], 0, 0)).toEqual([0, 0]); 6 | }); 7 | 8 | it("bearing should return positive value", () => { 9 | expect(destination([0, 0], 1, 1)).toEqual([ 10 | 0.00015695304633900835, 0.008991833928760623, 11 | ]); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/destination.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | import { 3 | degreesToRadians, 4 | lengthToRadians, 5 | radiansToDegrees, 6 | } from "../helpers"; 7 | import { CartesianPoint } from "../../common"; 8 | 9 | // Adapted from @turf/destination module which is MIT Licensed 10 | // https://github.com/Turfjs/turf/blob/master/packages/turf-desination/index.ts 11 | 12 | export function destination( 13 | origin: Position, 14 | distance: number, 15 | bearing: number, 16 | ): Position { 17 | const longitude1 = degreesToRadians(origin[0]); 18 | const latitude1 = degreesToRadians(origin[1]); 19 | const bearingRad = degreesToRadians(bearing); 20 | const radians = lengthToRadians(distance); 21 | 22 | const latitude2 = Math.asin( 23 | Math.sin(latitude1) * Math.cos(radians) + 24 | Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearingRad), 25 | ); 26 | const longitude2 = 27 | longitude1 + 28 | Math.atan2( 29 | Math.sin(bearingRad) * Math.sin(radians) * Math.cos(latitude1), 30 | Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2), 31 | ); 32 | const lng = radiansToDegrees(longitude2); 33 | const lat = radiansToDegrees(latitude2); 34 | 35 | return [lng, lat]; 36 | } 37 | 38 | // Function to create a destination point in Web Mercator projection 39 | export function webMercatorDestination( 40 | { x, y }: CartesianPoint, 41 | distance: number, 42 | bearing: number, 43 | ): CartesianPoint { 44 | // Convert origin to Web Mercator 45 | const bearingRad = degreesToRadians(bearing); 46 | 47 | // Calculate the destination coordinates 48 | const deltaX = distance * Math.cos(bearingRad); 49 | const deltaY = distance * Math.sin(bearingRad); 50 | 51 | const newX = x + deltaX; 52 | const newY = y + deltaY; 53 | 54 | return { x: newX, y: newY }; 55 | } 56 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/haversine-distance.spec.ts: -------------------------------------------------------------------------------- 1 | import { haversineDistanceKilometers } from "./haversine-distance"; 2 | 3 | describe("Geometry", () => { 4 | describe("haversineDistance", () => { 5 | it("distance between two identical points is zero", () => { 6 | expect(haversineDistanceKilometers([0, 0], [0, 0])).toBe(0); 7 | }); 8 | 9 | it("measures distance between two points", () => { 10 | expect( 11 | haversineDistanceKilometers([0.119, 52.205], [2.351, 48.857]), 12 | ).toBe(404.27916398867916); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/haversine-distance.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | 3 | export function haversineDistanceKilometers( 4 | pointOne: Position, 5 | pointTwo: Position, 6 | ) { 7 | const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180; 8 | 9 | const phiOne = toRadians(pointOne[1]); 10 | const lambdaOne = toRadians(pointOne[0]); 11 | const phiTwo = toRadians(pointTwo[1]); 12 | const lambdaTwo = toRadians(pointTwo[0]); 13 | const deltaPhi = phiTwo - phiOne; 14 | const deltalambda = lambdaTwo - lambdaOne; 15 | 16 | const a = 17 | Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) + 18 | Math.cos(phiOne) * 19 | Math.cos(phiTwo) * 20 | Math.sin(deltalambda / 2) * 21 | Math.sin(deltalambda / 2); 22 | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 23 | 24 | const radius = 6371e3; 25 | const distance = radius * c; 26 | 27 | return distance / 1000; 28 | } 29 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/pixel-distance-to-line.spec.ts: -------------------------------------------------------------------------------- 1 | import { pixelDistanceToLine } from "./pixel-distance-to-line"; 2 | 3 | const checkApprox = (num1: number, num2: number, epsilon = 0.00001) => { 4 | // Calculating the absolute difference 5 | // and compare with epsilon 6 | return Math.abs(num1 - num2) < epsilon; 7 | }; 8 | 9 | describe("Geometry", () => { 10 | describe("getPixelDistanceToLine", () => { 11 | it("point is start of line, distance should be zero", () => { 12 | const result = pixelDistanceToLine( 13 | { x: 0, y: 0 }, 14 | { x: 0, y: 0 }, 15 | { x: 1, y: 1 }, 16 | ); 17 | expect(result).toBe(0); 18 | }); 19 | 20 | it("point is end line, distance should be zero", () => { 21 | const result = pixelDistanceToLine( 22 | { x: 1, y: 1 }, 23 | { x: 0, y: 0 }, 24 | { x: 1, y: 1 }, 25 | ); 26 | expect(result).toBe(0); 27 | }); 28 | 29 | it("point is middle of line, distance should be zero", () => { 30 | const result = pixelDistanceToLine( 31 | { x: 0.5, y: 0.5 }, 32 | { x: 0, y: 0 }, 33 | { x: 1, y: 1 }, 34 | ); 35 | expect(result).toBe(0); 36 | }); 37 | 38 | it("point is off from line by 1", () => { 39 | const result = pixelDistanceToLine( 40 | { x: 1, y: 2 }, 41 | { x: 0, y: 0 }, 42 | { x: 1, y: 1 }, 43 | ); 44 | expect(result).toBe(1); 45 | }); 46 | 47 | it("point is off from line diagonally", () => { 48 | const result = pixelDistanceToLine( 49 | { x: 2, y: 2 }, 50 | { x: 0, y: 0 }, 51 | { x: 1, y: 1 }, 52 | ); 53 | expect(checkApprox(result, 1.4142135623730951)).toBe(true); 54 | }); 55 | 56 | it("handles line of zero length", () => { 57 | const result = pixelDistanceToLine( 58 | { x: 0, y: 0 }, 59 | { x: 0, y: 0 }, 60 | { x: 0, y: 0 }, 61 | ); 62 | expect(result).toBe(0); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/pixel-distance-to-line.ts: -------------------------------------------------------------------------------- 1 | import { CartesianPoint } from "../../common"; 2 | 3 | export const pixelDistanceToLine = ( 4 | point: CartesianPoint, 5 | linePointOne: CartesianPoint, 6 | linePointTwo: CartesianPoint, 7 | ) => { 8 | const square = (x: number) => { 9 | return x * x; 10 | }; 11 | const dist2 = (v: CartesianPoint, w: CartesianPoint) => { 12 | return square(v.x - w.x) + square(v.y - w.y); 13 | }; 14 | const distToSegmentSquared = ( 15 | p: CartesianPoint, 16 | v: CartesianPoint, 17 | w: CartesianPoint, 18 | ) => { 19 | const l2 = dist2(v, w); 20 | 21 | if (l2 === 0) { 22 | return dist2(p, v); 23 | } 24 | 25 | let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2; 26 | t = Math.max(0, Math.min(1, t)); 27 | 28 | return dist2(p, { x: v.x + t * (w.x - v.x), y: v.y + t * (w.y - v.y) }); 29 | }; 30 | 31 | return Math.sqrt(distToSegmentSquared(point, linePointOne, linePointTwo)); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/pixel-distance.spec.ts: -------------------------------------------------------------------------------- 1 | import { cartesianDistance } from "./pixel-distance"; 2 | 3 | const checkApprox = (num1: number, num2: number, epsilon = 0.00001) => { 4 | // Calculating the absolute difference 5 | // and compare with epsilon 6 | return Math.abs(num1 - num2) < epsilon; 7 | }; 8 | 9 | describe("Geometry", () => { 10 | describe("getPixelDistance", () => { 11 | it("vertical distance", () => { 12 | const result = cartesianDistance({ x: 0, y: 0 }, { x: 0, y: 1 }); 13 | expect(result).toBe(1); 14 | }); 15 | it("horizontal distance", () => { 16 | const result = cartesianDistance({ x: 0, y: 0 }, { x: 1, y: 0 }); 17 | expect(result).toBe(1); 18 | }); 19 | it("diagonal distance", () => { 20 | const result = cartesianDistance({ x: 0, y: 0 }, { x: 1, y: 1 }); 21 | expect(checkApprox(result, 1.4142135623730951)).toBe(true); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/pixel-distance.ts: -------------------------------------------------------------------------------- 1 | import { CartesianPoint } from "../../common"; 2 | 3 | export const cartesianDistance = ( 4 | pointOne: CartesianPoint, 5 | pointTwo: CartesianPoint, 6 | ) => { 7 | const { x: x1, y: y1 } = pointOne; 8 | const { x: x2, y: y2 } = pointTwo; 9 | const y = x2 - x1; 10 | const x = y2 - y1; 11 | return Math.sqrt(x * x + y * y); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/rhumb-bearing.spec.ts: -------------------------------------------------------------------------------- 1 | import { rhumbBearing } from "./rhumb-bearing"; 2 | 3 | describe("rhumbBearing", () => { 4 | it("gets rhumb bearing correctly", () => { 5 | const result = rhumbBearing([0, 0], [1, 1]); 6 | expect(result).toBe(44.99854548511024); 7 | }); 8 | 9 | it("gets rhumb bearing correctly", () => { 10 | for (let i = 0; i < 180; i++) { 11 | const result = rhumbBearing([i, i / 2], [-i, -(i / 2)]); 12 | const result2 = rhumbBearing([-i, -(i / 2)], [i, i / 2]); 13 | expect(typeof result).toBe("number"); 14 | expect(typeof result2).toBe("number"); 15 | } 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/rhumb-bearing.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | import { degreesToRadians, radiansToDegrees } from "../helpers"; 3 | 4 | // Based on Turf.js Rhumb Bearing module which is MIT Licensed 5 | // https://github.com/Turfjs/turf/blob/master/packages/turf-rhumb-bearing/index.ts 6 | 7 | export function rhumbBearing(start: Position, end: Position): number { 8 | const from = start; 9 | const to = end; 10 | 11 | // φ => phi 12 | // Δλ => deltaLambda 13 | // Δψ => deltaPsi 14 | // θ => theta 15 | const phi1 = degreesToRadians(from[1]); 16 | const phi2 = degreesToRadians(to[1]); 17 | let deltaLambda = degreesToRadians(to[0] - from[0]); 18 | 19 | // if deltaLambdaon over 180° take shorter rhumb line across the anti-meridian: 20 | if (deltaLambda > Math.PI) { 21 | deltaLambda -= 2 * Math.PI; 22 | } 23 | if (deltaLambda < -Math.PI) { 24 | deltaLambda += 2 * Math.PI; 25 | } 26 | 27 | const deltaPsi = Math.log( 28 | Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4), 29 | ); 30 | 31 | const theta = Math.atan2(deltaLambda, deltaPsi); 32 | 33 | const bear360 = (radiansToDegrees(theta) + 360) % 360; 34 | 35 | const bear180 = bear360 > 180 ? -(360 - bear360) : bear360; 36 | 37 | return bear180; 38 | } 39 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/rhumb-destination.spec.ts: -------------------------------------------------------------------------------- 1 | import { rhumbDestination } from "./rhumb-destination"; 2 | 3 | describe("rhumbDestination", () => { 4 | it("gets rhumb destination correctly", () => { 5 | const result = rhumbDestination([1, 1], 1000, 1); 6 | expect(result).toStrictEqual([1.0001569771690129, 1.008991833928772]); 7 | }); 8 | 9 | it("gets rhumb destination correctly with negative distance", () => { 10 | const result = rhumbDestination([1, 1], -1000, 1); 11 | expect(result).toStrictEqual([0.9998430232609508, 0.9910081660712281]); 12 | }); 13 | 14 | it("gets rhumb destination correctly", () => { 15 | const result = rhumbDestination([90, 180], -1000, 1); 16 | expect(result).toStrictEqual([90.000156953045, 0.008991833928770716]); 17 | }); 18 | 19 | it("gets rhumb destination correctly", () => { 20 | const result = rhumbDestination([-90, -180], -1000, 1); 21 | expect(result).toStrictEqual([-89.99984304695494, 0.008991833928770716]); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/rhumb-destination.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | import { degreesToRadians, earthRadius } from "../helpers"; 3 | 4 | // Based on @turf/rhumb-destination module which is MIT Licensed 5 | // https://github.com/Turfjs/turf/blob/master/packages/turf-rhumb-destination/index.ts 6 | 7 | export function rhumbDestination( 8 | origin: Position, 9 | distanceMeters: number, 10 | bearing: number, 11 | ): Position { 12 | const wasNegativeDistance = distanceMeters < 0; 13 | let distanceInMeters = distanceMeters; 14 | 15 | if (wasNegativeDistance) { 16 | distanceInMeters = -Math.abs(distanceInMeters); 17 | } 18 | 19 | const delta = distanceInMeters / earthRadius; // angular distance in radians 20 | const lambda1 = (origin[0] * Math.PI) / 180; // to radians, but without normalize to 𝜋 21 | const phi1 = degreesToRadians(origin[1]); 22 | const theta = degreesToRadians(bearing); 23 | 24 | const DeltaPhi = delta * Math.cos(theta); 25 | let phi2 = phi1 + DeltaPhi; 26 | 27 | // check for going past the pole, normalise latitude if so 28 | if (Math.abs(phi2) > Math.PI / 2) { 29 | phi2 = phi2 > 0 ? Math.PI - phi2 : -Math.PI - phi2; 30 | } 31 | 32 | const DeltaPsi = Math.log( 33 | Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4), 34 | ); 35 | // E-W course becomes ill-conditioned with 0/0 36 | const q = Math.abs(DeltaPsi) > 10e-12 ? DeltaPhi / DeltaPsi : Math.cos(phi1); 37 | 38 | const DeltaLambda = (delta * Math.sin(theta)) / q; 39 | const lambda2 = lambda1 + DeltaLambda; 40 | 41 | // normalise to −180..+180° 42 | const destination = [ 43 | (((lambda2 * 180) / Math.PI + 540) % 360) - 180, 44 | (phi2 * 180) / Math.PI, 45 | ]; 46 | 47 | // compensate the crossing of the 180th meridian (https://macwright.org/2016/09/26/the-180th-meridian.html) 48 | // solution from https://github.com/mapbox/mapbox-gl-js/issues/3250#issuecomment-294887678 49 | destination[0] += 50 | destination[0] - origin[0] > 180 51 | ? -360 52 | : origin[0] - destination[0] > 180 53 | ? 360 54 | : 0; 55 | return destination; 56 | } 57 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/rhumb-distance.spec.ts: -------------------------------------------------------------------------------- 1 | import { rhumbDistance } from "./rhumb-distance"; 2 | 3 | describe("rhumbDistance", () => { 4 | it("gets rhumb destination correctly", () => { 5 | const result = rhumbDistance([0, 0], [1, 1]); 6 | expect(result).toBe(157249.5986361746); 7 | }); 8 | 9 | it("gets rhumb destination correctly", () => { 10 | const result = rhumbDistance([-180, -90], [-180, 90]); 11 | expect(result).toBe(20015114.442035925); 12 | }); 13 | 14 | it("gets rhumb destination correctly", () => { 15 | const result = rhumbDistance([0, 0], [0, 0]); 16 | expect(result).toBe(0); 17 | }); 18 | 19 | it("gets rhumb destination correctly", () => { 20 | const result = rhumbDistance([180, 0], [180, 0]); 21 | expect(result).toBe(0); 22 | }); 23 | 24 | it("gets rhumb destination correctly", () => { 25 | const result = rhumbDistance([180, 90], [180, 90]); 26 | expect(result).toBe(0); 27 | }); 28 | 29 | it("gets rhumb destination correctly", () => { 30 | const result = rhumbDistance([-180, -90], [180, 90]); 31 | expect(result).toBe(20015114.442035925); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/rhumb-distance.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | import { earthRadius } from "../helpers"; 3 | 4 | // Adapted from @turf/rhumb-distance module 5 | // https://github.com/Turfjs/turf/blob/master/packages/turf-rhumb-distance/index.ts 6 | 7 | export function rhumbDistance(destination: Position, origin: Position): number { 8 | // compensate the crossing of the 180th meridian (https://macwright.org/2016/09/26/the-180th-meridian.html) 9 | // solution from https://github.com/mapbox/mapbox-gl-js/issues/3250#issuecomment-294887678 10 | destination[0] += 11 | destination[0] - origin[0] > 180 12 | ? -360 13 | : origin[0] - destination[0] > 180 14 | ? 360 15 | : 0; 16 | 17 | // see www.edwilliams.org/avform.htm#Rhumb 18 | 19 | const R = earthRadius; 20 | const phi1 = (origin[1] * Math.PI) / 180; 21 | const phi2 = (destination[1] * Math.PI) / 180; 22 | const DeltaPhi = phi2 - phi1; 23 | let DeltaLambda = (Math.abs(destination[0] - origin[0]) * Math.PI) / 180; 24 | 25 | // if dLon over 180° take shorter rhumb line across the anti-meridian: 26 | if (DeltaLambda > Math.PI) { 27 | DeltaLambda -= 2 * Math.PI; 28 | } 29 | 30 | // on Mercator projection, longitude distances shrink by latitude; q is the 'stretch factor' 31 | // q becomes ill-conditioned along E-W line (0/0); use empirical tolerance to avoid it 32 | const DeltaPsi = Math.log( 33 | Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4), 34 | ); 35 | const q = Math.abs(DeltaPsi) > 10e-12 ? DeltaPhi / DeltaPsi : Math.cos(phi1); 36 | 37 | // distance is pythagoras on 'stretched' Mercator projection 38 | const delta = Math.sqrt( 39 | DeltaPhi * DeltaPhi + q * q * DeltaLambda * DeltaLambda, 40 | ); // angular distance in radians 41 | 42 | const distanceMeters = delta * R; 43 | 44 | return distanceMeters; 45 | } 46 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/measure/slice-along.ts: -------------------------------------------------------------------------------- 1 | import { LineString, Position } from "geojson"; 2 | import { destination } from "./destination"; 3 | import { bearing } from "./bearing"; 4 | import { haversineDistanceKilometers } from "./haversine-distance"; 5 | 6 | // Adapted from @turf/line-slice-along module which is MIT licensed 7 | // https://github.com/Turfjs/turf/blob/master/packages/turf-line-slice-along/index.ts 8 | 9 | export function lineSliceAlong( 10 | coords: LineString["coordinates"], 11 | startDist: number, 12 | stopDist: number, 13 | ): Position[] { 14 | const slice: Position[] = []; 15 | 16 | const origCoordsLength = coords.length; 17 | 18 | let travelled = 0; 19 | let overshot, direction, interpolated; 20 | for (let i = 0; i < coords.length; i++) { 21 | if (startDist >= travelled && i === coords.length - 1) { 22 | break; 23 | } else if (travelled > startDist && slice.length === 0) { 24 | overshot = startDist - travelled; 25 | if (!overshot) { 26 | slice.push(coords[i]); 27 | return slice; 28 | } 29 | direction = bearing(coords[i], coords[i - 1]) - 180; 30 | interpolated = destination(coords[i], overshot, direction); 31 | slice.push(interpolated); 32 | } 33 | 34 | if (travelled >= stopDist) { 35 | overshot = stopDist - travelled; 36 | if (!overshot) { 37 | slice.push(coords[i]); 38 | return slice; 39 | } 40 | direction = bearing(coords[i], coords[i - 1]) - 180; 41 | interpolated = destination(coords[i], overshot, direction); 42 | slice.push(interpolated); 43 | return slice; 44 | } 45 | 46 | if (travelled >= startDist) { 47 | slice.push(coords[i]); 48 | } 49 | 50 | if (i === coords.length - 1) { 51 | return slice; 52 | } 53 | 54 | travelled += haversineDistanceKilometers(coords[i], coords[i + 1]); 55 | } 56 | 57 | if (travelled < startDist && coords.length === origCoordsLength) { 58 | throw new Error("Start position is beyond line"); 59 | } 60 | 61 | const last = coords[coords.length - 1]; 62 | return [last, last]; 63 | } 64 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/midpoint-coordinate.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | import { limitPrecision } from "./limit-decimal-precision"; 3 | import { Project, Unproject } from "../common"; 4 | import { haversineDistanceKilometers } from "./measure/haversine-distance"; 5 | import { rhumbBearing } from "./measure/rhumb-bearing"; 6 | import { rhumbDestination } from "./measure/rhumb-destination"; 7 | 8 | // midpointCoordinate is adapted from the @turf/midpoint which is MIT Licensed 9 | // https://github.com/Turfjs/turf/tree/master/packages/turf-midpoint 10 | 11 | export function midpointCoordinate( 12 | coordinates1: Position, 13 | coordinates2: Position, 14 | precision: number, 15 | project: Project, 16 | unproject: Unproject, 17 | ) { 18 | const projectedCoordinateOne = project(coordinates1[0], coordinates1[1]); 19 | const projectedCoordinateTwo = project(coordinates2[0], coordinates2[1]); 20 | 21 | const { lng, lat } = unproject( 22 | (projectedCoordinateOne.x + projectedCoordinateTwo.x) / 2, 23 | (projectedCoordinateOne.y + projectedCoordinateTwo.y) / 2, 24 | ); 25 | 26 | return [limitPrecision(lng, precision), limitPrecision(lat, precision)]; 27 | } 28 | 29 | /* Get the geodesic midpoint coordinate between two coordinates */ 30 | export function geodesicMidpointCoordinate( 31 | coordinates1: Position, 32 | coordinates2: Position, 33 | precision: number, 34 | ) { 35 | const dist = haversineDistanceKilometers(coordinates1, coordinates2) * 1000; 36 | const heading = rhumbBearing(coordinates1, coordinates2); 37 | const midpoint = rhumbDestination(coordinates1, dist / 2, heading); 38 | return [ 39 | limitPrecision(midpoint[0], precision), 40 | limitPrecision(midpoint[1], precision), 41 | ]; 42 | } 43 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/point-on-line.spec.ts: -------------------------------------------------------------------------------- 1 | import { nearestPointOnLine } from "./point-on-line"; 2 | 3 | describe("nearestPointOnLine", () => { 4 | it("returns exact feature coordinate if source coordinate is that coordinate", () => { 5 | const result = nearestPointOnLine( 6 | [0, 1], 7 | [ 8 | [ 9 | [0, 0], 10 | [0, 1], 11 | ], 12 | [ 13 | [0, 1], 14 | [1, 1], 15 | ], 16 | [ 17 | [1, 1], 18 | [1, 0], 19 | ], 20 | [ 21 | [1, 0], 22 | [0, 0], 23 | ], 24 | ], 25 | ); 26 | 27 | expect(result?.coordinate).toEqual([0, 1]); 28 | expect(result?.distance).toEqual(0); 29 | }); 30 | 31 | it("returns coordinate on line", () => { 32 | const result = nearestPointOnLine( 33 | [0, 2], 34 | [ 35 | [ 36 | [0, 0], 37 | [0, 1], 38 | ], 39 | [ 40 | [0, 1], 41 | [1, 1], 42 | ], 43 | [ 44 | [1, 1], 45 | [1, 0], 46 | ], 47 | [ 48 | [1, 0], 49 | [0, 0], 50 | ], 51 | ], 52 | ); 53 | 54 | expect(result?.coordinate[0]).toBeCloseTo(0); 55 | expect(result?.coordinate[1]).toBeCloseTo(1); 56 | 57 | expect(result?.distance).toBeCloseTo(111.19492664455873); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/project/web-mercator.spec.ts: -------------------------------------------------------------------------------- 1 | import { lngLatToWebMercatorXY, webMercatorXYToLngLat } from "./web-mercator"; 2 | 3 | describe("web mercator", () => { 4 | describe("lngLatToWebMercatorXY", () => { 5 | it("returns x,y of 0,0 web mercator coordinates from lng,lat of 0,0", () => { 6 | const result = lngLatToWebMercatorXY(0, 0); 7 | expect(result).toStrictEqual({ x: 0, y: 0 }); 8 | }); 9 | 10 | it("returns correct x,y web mercator coordinates for lng, lat", () => { 11 | const result = lngLatToWebMercatorXY(179, 89); 12 | expect(result).toStrictEqual({ 13 | x: 19926188.85199597, 14 | y: 30240971.958386205, 15 | }); 16 | }); 17 | 18 | it("returns correct x,y web mercator coordinates for lng, lat", () => { 19 | const result = lngLatToWebMercatorXY(-179, -89); 20 | expect(result).toStrictEqual({ 21 | x: -19926188.85199597, 22 | y: -30240971.95838617, 23 | }); 24 | }); 25 | }); 26 | 27 | describe("webMercatorXYToLngLat", () => { 28 | it("returns x,y of 0,0 web mercator coordinates from lng,lat of 0,0", () => { 29 | const result = webMercatorXYToLngLat(0, 0); 30 | expect(result).toStrictEqual({ lng: 0, lat: 0 }); 31 | }); 32 | 33 | it("returns correct x,y web mercator coordinates for lng, lat", () => { 34 | const result = webMercatorXYToLngLat( 35 | 19926188.85199597, 36 | 30240971.958386205, 37 | ); 38 | expect(result).toStrictEqual({ lng: 179, lat: 89.00000000000001 }); // TODO: should we limit precision? 39 | }); 40 | 41 | it("returns correct x,y web mercator coordinates for lng, lat", () => { 42 | const result = webMercatorXYToLngLat( 43 | -19926188.85199597, 44 | -30240971.95838617, 45 | ); 46 | expect(result).toStrictEqual({ lng: -179, lat: -89 }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/project/web-mercator.ts: -------------------------------------------------------------------------------- 1 | import { CartesianPoint } from "../../common"; 2 | 3 | const RADIANS_TO_DEGREES = 57.29577951308232 as const; // 180 / Math.PI 4 | const DEGREES_TO_RADIANS = 0.017453292519943295 as const; // Math.PI / 180 5 | const R = 6378137 as const; 6 | 7 | /** 8 | * Convert longitude and latitude to web mercator x and y 9 | * @param lng 10 | * @param lat 11 | * @returns - web mercator x and y 12 | */ 13 | export const lngLatToWebMercatorXY = ( 14 | lng: number, 15 | lat: number, 16 | ): CartesianPoint => ({ 17 | x: lng === 0 ? 0 : lng * DEGREES_TO_RADIANS * R, 18 | y: 19 | lat === 0 20 | ? 0 21 | : Math.log(Math.tan(Math.PI / 4 + (lat * DEGREES_TO_RADIANS) / 2)) * R, 22 | }); 23 | 24 | /** 25 | * Convert web mercator x and y to longitude and latitude 26 | * @param x - web mercator x 27 | * @param y - web mercator y 28 | * @returns - longitude and latitude 29 | */ 30 | export const webMercatorXYToLngLat = ( 31 | x: number, 32 | y: number, 33 | ): { lng: number; lat: number } => ({ 34 | lng: x === 0 ? 0 : RADIANS_TO_DEGREES * (x / R), 35 | lat: 36 | y === 0 37 | ? 0 38 | : (2 * Math.atan(Math.exp(y / R)) - Math.PI / 2) * RADIANS_TO_DEGREES, 39 | }); 40 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/shape/create-bbox.spec.ts: -------------------------------------------------------------------------------- 1 | import { Unproject } from "../../common"; 2 | import { createBBoxFromPoint } from "./create-bbox"; 3 | 4 | describe("Geometry", () => { 5 | describe("bbox", () => { 6 | it("creates a bbox polygon", () => { 7 | const unproject = jest.fn((x, y) => ({ 8 | lng: x, 9 | lat: y, 10 | })) as unknown as Unproject; 11 | 12 | const result = createBBoxFromPoint({ 13 | point: { 14 | x: 10, 15 | y: 10, 16 | }, 17 | unproject, 18 | pointerDistance: 30, 19 | }); 20 | 21 | expect(result.geometry.type).toBe("Polygon"); 22 | expect(result.geometry.coordinates).toEqual([ 23 | [ 24 | [-5, -5], 25 | [25, -5], 26 | [25, 25], 27 | [-5, 25], 28 | [-5, -5], 29 | ], 30 | ]); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/shape/create-bbox.ts: -------------------------------------------------------------------------------- 1 | import { Feature, Polygon } from "geojson"; 2 | import { Unproject } from "../../common"; 3 | 4 | export function createBBoxFromPoint({ 5 | unproject, 6 | point, 7 | pointerDistance, 8 | }: { 9 | point: { 10 | x: number; 11 | y: number; 12 | }; 13 | unproject: Unproject; 14 | pointerDistance: number; 15 | }) { 16 | const halfDist = pointerDistance / 2; 17 | const { x, y } = point; 18 | 19 | return { 20 | type: "Feature", 21 | properties: {}, 22 | geometry: { 23 | type: "Polygon", 24 | coordinates: [ 25 | [ 26 | unproject(x - halfDist, y - halfDist), // TopLeft 27 | unproject(x + halfDist, y - halfDist), // TopRight 28 | unproject(x + halfDist, y + halfDist), // BottomRight 29 | unproject(x - halfDist, y + halfDist), // BottomLeft 30 | unproject(x - halfDist, y - halfDist), // TopLeft 31 | ].map((c) => [c.lng, c.lat]), 32 | ], 33 | }, 34 | } as Feature; 35 | } 36 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/shape/great-circle-coordinates.spec.ts: -------------------------------------------------------------------------------- 1 | import { generateGreatCircleCoordinates } from "./great-circle-coordinates"; 2 | 3 | describe("Geometry", () => { 4 | describe("generateGreatCircleCoordinates", () => { 5 | it("generates a coordinate", () => { 6 | const result = generateGreatCircleCoordinates([0, 0], [1, 1], 1); 7 | expect(result).toHaveLength(1); 8 | expect(result).toEqual([[0.4999619199226218, 0.5000190382261059]]); 9 | }); 10 | 11 | it("generates many coordinates", () => { 12 | const result = generateGreatCircleCoordinates([0, 0], [1, 1], 10); 13 | expect(result).toHaveLength(10); 14 | expect(result).toEqual([ 15 | [0.09089993580370964, 0.09091366797545271], 16 | [0.18180032933571574, 0.18182710712118227], 17 | [0.2727016383289259, 0.27274008860343246], 18 | [0.3636043205254701, 0.3636523835803809], 19 | [0.4545088336813104, 0.4545637631981049], 20 | [0.5454156355708523, 0.5454739985865489], 21 | [0.6363251839915536, 0.6363828608554906], 22 | [0.7272379367685333, 0.7272901210905058], 23 | [0.8181543517591787, 0.8181955503489342], 24 | [0.9090748868577531, 0.9090989196558447], 25 | ]); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/shape/great-circle-coordinates.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | 3 | function toRadians(degrees: number): number { 4 | return degrees * (Math.PI / 180); 5 | } 6 | 7 | function toDegrees(radians: number): number { 8 | return radians * (180 / Math.PI); 9 | } 10 | 11 | export function generateGreatCircleCoordinates( 12 | start: Position, 13 | end: Position, 14 | numberOfPoints: number, 15 | ): Position[] { 16 | const points: Position[] = []; 17 | 18 | const lat1 = toRadians(start[1]); 19 | const lon1 = toRadians(start[0]); 20 | const lat2 = toRadians(end[1]); 21 | const lon2 = toRadians(end[0]); 22 | 23 | numberOfPoints += 1; 24 | 25 | // Calculate the angular distance between the two points using the Haversine formula 26 | const d = 27 | 2 * 28 | Math.asin( 29 | Math.sqrt( 30 | Math.sin((lat2 - lat1) / 2) ** 2 + 31 | Math.cos(lat1) * Math.cos(lat2) * Math.sin((lon2 - lon1) / 2) ** 2, 32 | ), 33 | ); 34 | 35 | if (d === 0 || isNaN(d)) { 36 | // Start and end coordinates are the same, or distance calculation failed, return empty array 37 | return points; 38 | } 39 | 40 | for (let i = 0; i <= numberOfPoints; i++) { 41 | const f = i / numberOfPoints; // Fraction of the total distance for the current point 42 | const A = Math.sin((1 - f) * d) / Math.sin(d); // Interpolation factor A 43 | const B = Math.sin(f * d) / Math.sin(d); // Interpolation factor B 44 | 45 | // Calculate the x, y, z coordinates of the intermediate point 46 | const x = 47 | A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2) * Math.cos(lon2); 48 | const y = 49 | A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2) * Math.sin(lon2); 50 | const z = A * Math.sin(lat1) + B * Math.sin(lat2); 51 | 52 | // Calculate the latitude and longitude of the intermediate point from the x, y, z coordinates 53 | if (isNaN(x) || isNaN(y) || isNaN(z)) { 54 | // Skip this point if any coordinate is NaN 55 | continue; 56 | } 57 | 58 | const lat = Math.atan2(z, Math.sqrt(x ** 2 + y ** 2)); 59 | const lon = Math.atan2(y, x); 60 | 61 | if (isNaN(lat) || isNaN(lon)) { 62 | // Skip this point if any coordinate is NaN 63 | continue; 64 | } 65 | 66 | points.push([toDegrees(lon), toDegrees(lat)]); 67 | } 68 | 69 | return points.slice(1, -1); 70 | } 71 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/shape/web-mercator-distortion.spec.ts: -------------------------------------------------------------------------------- 1 | import { calculateWebMercatorDistortion } from "./web-mercator-distortion"; 2 | 3 | describe("Geometry", () => { 4 | describe("calculateWebMercatorDistortion", () => { 5 | it("returns 1 for identical coordinates", () => { 6 | const result = calculateWebMercatorDistortion([0, 0], [0, 0]); 7 | expect(result).toBe(1); 8 | }); 9 | 10 | it("returns almost 1 for coordinates along the equator", () => { 11 | const result = calculateWebMercatorDistortion([0, 0], [179, 0]); 12 | expect(result).toBe(1.0011202323026207); 13 | }); 14 | 15 | it("returns high value for high latitudes", () => { 16 | const result = calculateWebMercatorDistortion([0, 0], [0, 89]); 17 | expect(result).toBe(3.0557707265347394); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/shape/web-mercator-distortion.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | import { haversineDistanceKilometers } from "../measure/haversine-distance"; 3 | import { lngLatToWebMercatorXY } from "../project/web-mercator"; 4 | 5 | /* 6 | * Function to calculate the web mercator vs geodesic distortion between two coordinates 7 | * Value of 1 means no distortion, higher values mean higher distortion 8 | * */ 9 | export function calculateWebMercatorDistortion( 10 | source: Position, 11 | target: Position, 12 | ): number { 13 | const geodesicDistance = haversineDistanceKilometers(source, target) * 1000; 14 | if (geodesicDistance === 0) { 15 | return 1; 16 | } 17 | 18 | const { x: x1, y: y1 } = lngLatToWebMercatorXY(source[0], source[1]); 19 | const { x: x2, y: y2 } = lngLatToWebMercatorXY(target[0], target[1]); 20 | const euclideanDistance = Math.sqrt( 21 | Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2), 22 | ); 23 | return euclideanDistance / geodesicDistance; 24 | } 25 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/web-mercator-centroid.spec.ts: -------------------------------------------------------------------------------- 1 | import { Feature, Polygon } from "geojson"; 2 | import { webMercatorCentroid } from "./web-mercator-centroid"; 3 | 4 | describe("webMercatorCentroid", () => { 5 | it("returns the web mercator center correctly for polygon", () => { 6 | const result = webMercatorCentroid({ 7 | type: "Feature", 8 | properties: {}, 9 | geometry: { 10 | coordinates: [ 11 | [ 12 | [60.15177901043299, 18.599398042438764], 13 | [60.15177901043299, 12.900268744312697], 14 | [63.75086634124065, 12.900268744312697], 15 | [63.75086634124065, 18.599398042438764], 16 | [60.15177901043299, 18.599398042438764], 17 | ], 18 | ], 19 | type: "Polygon", 20 | }, 21 | }); 22 | 23 | expect(result.x).toBeCloseTo(6896389.694243949); 24 | expect(result.y).toBeCloseTo(1778084.1594212381); 25 | }); 26 | 27 | it("returns the web mercator center correctly for polygon deterministically for the environment", () => { 28 | const polygon = { 29 | type: "Feature", 30 | properties: {}, 31 | geometry: { 32 | coordinates: [ 33 | [ 34 | [60.15177901043299, 18.599398042438764], 35 | [60.15177901043299, 12.900268744312697], 36 | [63.75086634124065, 12.900268744312697], 37 | [63.75086634124065, 18.599398042438764], 38 | [60.15177901043299, 18.599398042438764], 39 | ], 40 | ], 41 | type: "Polygon", 42 | }, 43 | } as Feature; 44 | const result = webMercatorCentroid(polygon); 45 | const result2 = webMercatorCentroid(polygon); 46 | 47 | expect(result.x).toEqual(result2.x); 48 | expect(result.y).toEqual(result2.y); 49 | }); 50 | 51 | it("returns the web mercator center correctly for linestring", () => { 52 | const result = webMercatorCentroid({ 53 | type: "Feature", 54 | properties: {}, 55 | geometry: { 56 | coordinates: [ 57 | [60.15177901043299, 18.599398042438764], 58 | [60.15177901043299, 12.900268744312697], 59 | [63.75086634124065, 12.900268744312697], 60 | ], 61 | type: "LineString", 62 | }, 63 | }); 64 | 65 | expect(result.x).toBeCloseTo(6829614.932746265); 66 | expect(result.y).toBeCloseTo(1668169.6040318909); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/web-mercator-centroid.ts: -------------------------------------------------------------------------------- 1 | import { Feature, LineString, Polygon, Position } from "geojson"; 2 | import { lngLatToWebMercatorXY } from "./project/web-mercator"; 3 | import { CartesianPoint } from "../common"; 4 | 5 | /** 6 | * Calculates the centroid of a GeoJSON Polygon or LineString in Web Mercator 7 | 8 | * @param {Feature} feature - The GeoJSON Feature containing either a Polygon or LineString 9 | * @returns {{ x: number, y: number }} The centroid of the polygon or line string in Web Mercator coordinates. 10 | */ 11 | export function webMercatorCentroid(feature: Feature) { 12 | const coordinates = 13 | feature.geometry.type === "Polygon" 14 | ? feature.geometry.coordinates[0] 15 | : feature.geometry.coordinates; 16 | 17 | const webMercatorCoordinates = coordinates.map((coord) => { 18 | const { x, y } = lngLatToWebMercatorXY(coord[0], coord[1]); 19 | return [x, y]; 20 | }); 21 | 22 | if (feature.geometry.type === "Polygon") { 23 | return calculatePolygonCentroid(webMercatorCoordinates); 24 | } else { 25 | return calculateLineStringMidpoint(webMercatorCoordinates); 26 | } 27 | } 28 | 29 | function calculatePolygonCentroid( 30 | webMercatorCoordinates: Position[], 31 | ): CartesianPoint { 32 | let area = 0; 33 | let centroidX = 0; 34 | let centroidY = 0; 35 | 36 | const n = webMercatorCoordinates.length; 37 | 38 | for (let i = 0; i < n - 1; i++) { 39 | const [x1, y1] = webMercatorCoordinates[i]; 40 | const [x2, y2] = webMercatorCoordinates[i + 1]; 41 | 42 | const crossProduct = x1 * y2 - x2 * y1; 43 | area += crossProduct; 44 | centroidX += (x1 + x2) * crossProduct; 45 | centroidY += (y1 + y2) * crossProduct; 46 | } 47 | 48 | area /= 2; 49 | centroidX /= 6 * area; 50 | centroidY /= 6 * area; 51 | 52 | return { x: centroidX, y: centroidY }; 53 | } 54 | 55 | function calculateLineStringMidpoint(lineString: Position[]): CartesianPoint { 56 | const n = lineString.length; 57 | let totalX = 0; 58 | let totalY = 0; 59 | 60 | for (let i = 0; i < n; i++) { 61 | const [x, y] = lineString[i]; 62 | totalX += x; 63 | totalY += y; 64 | } 65 | 66 | return { x: totalX / n, y: totalY / n }; 67 | } 68 | -------------------------------------------------------------------------------- /packages/terra-draw/src/geometry/web-mercator-point-on-line.spec.ts: -------------------------------------------------------------------------------- 1 | import { webMercatorNearestPointOnLine } from "./web-mercator-point-on-line"; 2 | 3 | describe("webMercatorNearestPointOnLine", () => { 4 | it("returns exact feature coordinate if source coordinate is that coordinate", () => { 5 | const result = webMercatorNearestPointOnLine( 6 | [0, 1], 7 | [ 8 | [ 9 | [0, 0], 10 | [0, 1], 11 | ], 12 | [ 13 | [0, 1], 14 | [1, 1], 15 | ], 16 | [ 17 | [1, 1], 18 | [1, 0], 19 | ], 20 | [ 21 | [1, 0], 22 | [0, 0], 23 | ], 24 | ], 25 | ); 26 | 27 | expect(result?.coordinate).toEqual([0, 1]); 28 | expect(result?.distance).toEqual(0); 29 | }); 30 | 31 | it("returns coordinate on line", () => { 32 | const result = webMercatorNearestPointOnLine( 33 | [0, 2], 34 | [ 35 | [ 36 | [0, 0], 37 | [0, 1], 38 | ], 39 | [ 40 | [0, 1], 41 | [1, 1], 42 | ], 43 | [ 44 | [1, 1], 45 | [1, 0], 46 | ], 47 | [ 48 | [1, 0], 49 | [0, 0], 50 | ], 51 | ], 52 | ); 53 | 54 | expect(result?.coordinate[0]).toBeCloseTo(0); 55 | expect(result?.coordinate[1]).toBeCloseTo(1); 56 | 57 | expect(result?.distance).toBeCloseTo(111359.06563915969); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/base.behavior.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Project, 3 | Projection, 4 | TerraDrawGeoJSONStore, 5 | Unproject, 6 | } from "../common"; 7 | 8 | export type BehaviorConfig = { 9 | store: TerraDrawGeoJSONStore; 10 | mode: string; 11 | project: Project; 12 | unproject: Unproject; 13 | pointerDistance: number; 14 | coordinatePrecision: number; 15 | projection: Projection; 16 | }; 17 | 18 | export class TerraDrawModeBehavior { 19 | protected store: TerraDrawGeoJSONStore; 20 | protected mode: string; 21 | protected project: Project; 22 | protected unproject: Unproject; 23 | protected pointerDistance: number; 24 | protected coordinatePrecision: number; 25 | protected projection: Projection; 26 | 27 | constructor({ 28 | store, 29 | mode, 30 | project, 31 | unproject, 32 | pointerDistance, 33 | coordinatePrecision, 34 | projection, 35 | }: BehaviorConfig) { 36 | this.store = store; 37 | this.mode = mode; 38 | this.project = project; 39 | this.unproject = unproject; 40 | this.pointerDistance = pointerDistance; 41 | this.coordinatePrecision = coordinatePrecision; 42 | this.projection = projection; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/click-bounding-box.behavior.spec.ts: -------------------------------------------------------------------------------- 1 | import { MockBehaviorConfig } from "../test/mock-behavior-config"; 2 | import { MockCursorEvent } from "../test/mock-cursor-event"; 3 | import { ClickBoundingBoxBehavior } from "./click-bounding-box.behavior"; 4 | 5 | describe("ClickBoundingBoxBehavior", () => { 6 | describe("constructor", () => { 7 | it("constructs", () => { 8 | new ClickBoundingBoxBehavior(MockBehaviorConfig("test")); 9 | }); 10 | }); 11 | 12 | describe("api", () => { 13 | it("create", () => { 14 | const config = MockBehaviorConfig("test"); 15 | 16 | const clickBoundingBoxBehavior = new ClickBoundingBoxBehavior(config); 17 | 18 | const bbox = clickBoundingBoxBehavior.create( 19 | MockCursorEvent({ lng: 1, lat: 1 }), 20 | ); 21 | 22 | expect(bbox).toStrictEqual({ 23 | geometry: { 24 | coordinates: [ 25 | [ 26 | [0.5, 0.5], 27 | [1.5, 0.5], 28 | [1.5, 1.5], 29 | [0.5, 1.5], 30 | [0.5, 0.5], 31 | ], 32 | ], 33 | type: "Polygon", 34 | }, 35 | properties: {}, 36 | type: "Feature", 37 | }); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/click-bounding-box.behavior.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorConfig, TerraDrawModeBehavior } from "./base.behavior"; 2 | import { TerraDrawMouseEvent } from "../common"; 3 | import { createBBoxFromPoint } from "../geometry/shape/create-bbox"; 4 | 5 | export class ClickBoundingBoxBehavior extends TerraDrawModeBehavior { 6 | constructor(config: BehaviorConfig) { 7 | super(config); 8 | } 9 | 10 | public create(event: TerraDrawMouseEvent) { 11 | const { containerX: x, containerY: y } = event; 12 | return createBBoxFromPoint({ 13 | unproject: this.unproject, 14 | point: { x, y }, 15 | pointerDistance: this.pointerDistance, 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/coordinate-snapping.behavior.spec.ts: -------------------------------------------------------------------------------- 1 | import { createStorePolygon } from "../test/create-store-features"; 2 | import { MockBehaviorConfig } from "../test/mock-behavior-config"; 3 | import { MockCursorEvent } from "../test/mock-cursor-event"; 4 | import { BehaviorConfig } from "./base.behavior"; 5 | import { ClickBoundingBoxBehavior } from "./click-bounding-box.behavior"; 6 | import { PixelDistanceBehavior } from "./pixel-distance.behavior"; 7 | import { CoordinateSnappingBehavior } from "./coordinate-snapping.behavior"; 8 | 9 | describe("CoordinateSnappingBehavior", () => { 10 | describe("constructor", () => { 11 | it("constructs", () => { 12 | const config = MockBehaviorConfig("test"); 13 | new CoordinateSnappingBehavior( 14 | config, 15 | new PixelDistanceBehavior(config), 16 | new ClickBoundingBoxBehavior(config), 17 | ); 18 | }); 19 | }); 20 | 21 | describe("api", () => { 22 | let config: BehaviorConfig; 23 | let coordinateSnappingBehavior: CoordinateSnappingBehavior; 24 | 25 | beforeEach(() => { 26 | config = MockBehaviorConfig("test"); 27 | coordinateSnappingBehavior = new CoordinateSnappingBehavior( 28 | config, 29 | new PixelDistanceBehavior(config), 30 | new ClickBoundingBoxBehavior(config), 31 | ); 32 | }); 33 | 34 | describe("getSnappableCoordinate", () => { 35 | it("returns undefined if not snappable", () => { 36 | const snappedCoord = coordinateSnappingBehavior.getSnappableCoordinate( 37 | MockCursorEvent({ lng: 0, lat: 0 }), 38 | "mockId", 39 | ); 40 | 41 | expect(snappedCoord).toBe(undefined); 42 | }); 43 | 44 | it("returns a snappable coordinate if one exists", () => { 45 | // Ensure there is something to snap too by 46 | // creating an existing polygon 47 | createStorePolygon(config); 48 | 49 | const snappedCoord = coordinateSnappingBehavior.getSnappableCoordinate( 50 | MockCursorEvent({ lng: 0, lat: 0 }), 51 | "currentId", 52 | ); 53 | 54 | expect(snappedCoord).toStrictEqual([0, 0]); 55 | }); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/line-snapping.behavior.spec.ts: -------------------------------------------------------------------------------- 1 | import { createStorePolygon } from "../test/create-store-features"; 2 | import { MockBehaviorConfig } from "../test/mock-behavior-config"; 3 | import { MockCursorEvent } from "../test/mock-cursor-event"; 4 | import { BehaviorConfig } from "./base.behavior"; 5 | import { ClickBoundingBoxBehavior } from "./click-bounding-box.behavior"; 6 | import { PixelDistanceBehavior } from "./pixel-distance.behavior"; 7 | import { LineSnappingBehavior } from "./line-snapping.behavior"; 8 | 9 | describe("LineSnappingBehavior", () => { 10 | describe("constructor", () => { 11 | it("constructs", () => { 12 | const config = MockBehaviorConfig("test"); 13 | new LineSnappingBehavior( 14 | config, 15 | new PixelDistanceBehavior(config), 16 | new ClickBoundingBoxBehavior(config), 17 | ); 18 | }); 19 | }); 20 | 21 | describe("api", () => { 22 | let config: BehaviorConfig; 23 | let lineSnappingBehavior: LineSnappingBehavior; 24 | 25 | describe.each(["globe", "web-mercator"] as const)( 26 | "getSnappableCoordinateFirstClick (%s)", 27 | (projection) => { 28 | beforeEach(() => { 29 | config = MockBehaviorConfig("test", projection); 30 | lineSnappingBehavior = new LineSnappingBehavior( 31 | config, 32 | new PixelDistanceBehavior(config), 33 | new ClickBoundingBoxBehavior(config), 34 | ); 35 | }); 36 | 37 | describe("getSnappableCoordinate", () => { 38 | it("returns undefined if not snappable", () => { 39 | const snappedCoord = lineSnappingBehavior.getSnappableCoordinate( 40 | MockCursorEvent({ lng: 0, lat: 0 }), 41 | "mockId", 42 | ); 43 | 44 | expect(snappedCoord).toBe(undefined); 45 | }); 46 | 47 | it("returns a snappable coordinate if one exists", () => { 48 | // Ensure there is something to snap too by 49 | // creating an existing polygon 50 | createStorePolygon(config); 51 | 52 | const snappedCoord = lineSnappingBehavior.getSnappableCoordinate( 53 | MockCursorEvent({ lng: 0, lat: 0 }), 54 | "currentId", 55 | ); 56 | 57 | expect(snappedCoord).toStrictEqual([0, 0]); 58 | }); 59 | 60 | it("returns a snappable coordinate if one exists", () => { 61 | // Ensure there is something to snap too by 62 | // creating an existing polygon 63 | createStorePolygon(config); 64 | 65 | const snappedCoord = lineSnappingBehavior.getSnappableCoordinate( 66 | MockCursorEvent({ lng: -0.5, lat: 0.5 }), 67 | "currentId", 68 | ); 69 | 70 | expect(snappedCoord && snappedCoord[0]).toBeCloseTo(0); 71 | expect(snappedCoord && snappedCoord[1]).toBeCloseTo( 72 | 0.5000190382262164, 73 | ); 74 | }); 75 | }); 76 | }, 77 | ); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/pixel-distance.behavior.spec.ts: -------------------------------------------------------------------------------- 1 | import { MockBehaviorConfig } from "../test/mock-behavior-config"; 2 | import { MockCursorEvent } from "../test/mock-cursor-event"; 3 | import { PixelDistanceBehavior } from "./pixel-distance.behavior"; 4 | 5 | describe("PixelDistanceBehavior", () => { 6 | describe("constructor", () => { 7 | it("constructs", () => { 8 | new PixelDistanceBehavior(MockBehaviorConfig("test")); 9 | }); 10 | }); 11 | 12 | describe("api", () => { 13 | it("measure", () => { 14 | const config = MockBehaviorConfig("test"); 15 | 16 | const pixelDistanceBehavior = new PixelDistanceBehavior(config); 17 | 18 | const distance = pixelDistanceBehavior.measure( 19 | MockCursorEvent({ lng: 0, lat: 0 }), 20 | [0, 1], 21 | ); 22 | 23 | // The mockBehaviorConfig function returns project/unproject methods that work on 40 pixel increments 24 | expect(distance).toStrictEqual(40); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/pixel-distance.behavior.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorConfig, TerraDrawModeBehavior } from "./base.behavior"; 2 | import { TerraDrawMouseEvent } from "../common"; 3 | 4 | import { Position } from "geojson"; 5 | import { cartesianDistance } from "../geometry/measure/pixel-distance"; 6 | 7 | export class PixelDistanceBehavior extends TerraDrawModeBehavior { 8 | constructor(config: BehaviorConfig) { 9 | super(config); 10 | } 11 | public measure(clickEvent: TerraDrawMouseEvent, secondCoordinate: Position) { 12 | const { x, y } = this.project(secondCoordinate[0], secondCoordinate[1]); 13 | 14 | const distance = cartesianDistance( 15 | { x, y }, 16 | { x: clickEvent.containerX, y: clickEvent.containerY }, 17 | ); 18 | 19 | return distance; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/select/behaviors/scale-feature.behavior.ts: -------------------------------------------------------------------------------- 1 | import { TerraDrawMouseEvent, Validation } from "../../../common"; 2 | import { BehaviorConfig, TerraDrawModeBehavior } from "../../base.behavior"; 3 | import { FeatureId } from "../../../store/store"; 4 | import { DragCoordinateResizeBehavior } from "./drag-coordinate-resize.behavior"; 5 | 6 | export class ScaleFeatureBehavior extends TerraDrawModeBehavior { 7 | constructor( 8 | readonly config: BehaviorConfig, 9 | private readonly dragCoordinateResizeBehavior: DragCoordinateResizeBehavior, 10 | ) { 11 | super(config); 12 | } 13 | 14 | public scale( 15 | event: TerraDrawMouseEvent, 16 | featureId: FeatureId, 17 | validation?: Validation, 18 | ) { 19 | if (!this.dragCoordinateResizeBehavior.isDragging()) { 20 | const index = this.dragCoordinateResizeBehavior.getDraggableIndex( 21 | event, 22 | featureId, 23 | ); 24 | this.dragCoordinateResizeBehavior.startDragging(featureId, index); 25 | } 26 | 27 | this.dragCoordinateResizeBehavior.drag(event, "center-fixed", validation); 28 | } 29 | 30 | public reset() { 31 | this.dragCoordinateResizeBehavior.stopDragging(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/select/behaviors/selection-point.behavior.ts: -------------------------------------------------------------------------------- 1 | import { LineString, Point, Polygon, Position } from "geojson"; 2 | import { BehaviorConfig, TerraDrawModeBehavior } from "../../base.behavior"; 3 | import { getCoordinatesAsPoints } from "../../../geometry/get-coordinates-as-points"; 4 | import { FeatureId } from "../../../store/store"; 5 | 6 | export class SelectionPointBehavior extends TerraDrawModeBehavior { 7 | constructor(config: BehaviorConfig) { 8 | super(config); 9 | } 10 | 11 | private _selectionPoints: FeatureId[] = []; 12 | 13 | get ids() { 14 | return this._selectionPoints.concat(); 15 | } 16 | 17 | set ids(_: FeatureId[]) {} 18 | 19 | public create( 20 | selectedCoords: Position[], 21 | type: Polygon["type"] | LineString["type"], 22 | featureId: FeatureId, 23 | ) { 24 | this._selectionPoints = this.store.create( 25 | getCoordinatesAsPoints(selectedCoords, type, (i) => ({ 26 | mode: this.mode, 27 | selectionPoint: true, 28 | selectionPointFeatureId: featureId, 29 | index: i, 30 | })), 31 | ); 32 | } 33 | 34 | public delete() { 35 | if (this.ids.length) { 36 | this.store.delete(this.ids); 37 | this._selectionPoints = []; 38 | } 39 | } 40 | 41 | public getUpdated(updatedCoordinates: Position[]) { 42 | if (this._selectionPoints.length === 0) { 43 | return undefined; 44 | } 45 | 46 | return this._selectionPoints.map((id, i) => { 47 | return { 48 | id, 49 | geometry: { 50 | type: "Point", 51 | coordinates: updatedCoordinates[i], 52 | } as Point, 53 | }; 54 | }); 55 | } 56 | 57 | public getOneUpdated(index: number, updatedCoordinate: Position) { 58 | if (this._selectionPoints[index] === undefined) { 59 | return undefined; 60 | } 61 | 62 | return { 63 | id: this._selectionPoints[index] as string, 64 | geometry: { 65 | type: "Point", 66 | coordinates: updatedCoordinate, 67 | } as Point, 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/terra-draw/src/modes/static/static.mode.ts: -------------------------------------------------------------------------------- 1 | import { TerraDrawAdapterStyling } from "../../common"; 2 | import { getDefaultStyling } from "../../util/styling"; 3 | import { ModeTypes, TerraDrawBaseDrawMode } from "../base.mode"; 4 | 5 | type StaticModeStylingExt = {}; 6 | type StaticModeStyling = StaticModeStylingExt; 7 | 8 | export class TerraDrawStaticMode extends TerraDrawBaseDrawMode { 9 | type = ModeTypes.Static; 10 | mode = "static" as const; 11 | start() {} 12 | stop() {} 13 | onKeyUp() {} 14 | onKeyDown() {} 15 | onClick() {} 16 | onDragStart() {} 17 | onDrag() {} 18 | onDragEnd() {} 19 | onMouseMove() {} 20 | cleanUp() {} 21 | styleFeature() { 22 | return { ...getDefaultStyling() }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/terra-draw/src/store/spatial-index/quickselect.spec.ts: -------------------------------------------------------------------------------- 1 | import { quickselect } from "./quickselect"; 2 | 3 | describe("quickselect", () => { 4 | test("selection rearranges items so that all items in the [left, k] are the smallest", function () { 5 | const arr = [65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39]; 6 | quickselect(arr, 8, 0, arr.length - 1, (a: number, b: number) => { 7 | return a < b ? -1 : a > b ? 1 : 0; 8 | }); 9 | 10 | expect(arr).toStrictEqual([ 11 | 39, 28, 28, 33, 21, 12, 22, 50, 53, 56, 59, 65, 90, 77, 95, 12 | ]); 13 | }); 14 | 15 | test("selection where right - left > 600", function () { 16 | let arr = [65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39]; 17 | 18 | for (let i = 0; i < 601; i++) { 19 | arr = arr.concat([ 20 | 65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39, 21 | ]); 22 | } 23 | 24 | quickselect(arr, 8, 0, arr.length - 1, (a: number, b: number) => { 25 | return a < b ? -1 : a > b ? 1 : 0; 26 | }); 27 | 28 | expect(arr); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/terra-draw/src/store/spatial-index/quickselect.ts: -------------------------------------------------------------------------------- 1 | // ISC License 2 | // Copyright (c) 2018, Vladimir Agafonkin 3 | 4 | export type CompareFunction = (a: T, b: T) => number; 5 | 6 | export function quickselect( 7 | arr: T[], 8 | k: number, 9 | left: number, 10 | right: number, 11 | compare: CompareFunction, 12 | ) { 13 | while (right > left) { 14 | if (right - left > 600) { 15 | const n = right - left + 1; 16 | const m = k - left + 1; 17 | const z = Math.log(n); 18 | const s = 0.5 * Math.exp((2 * z) / 3); 19 | const sd = 20 | 0.5 * Math.sqrt((z * s * (n - s)) / n) * (m - n / 2 < 0 ? -1 : 1); 21 | const newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd)); 22 | const newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd)); 23 | quickselect(arr, k, newLeft, newRight, compare); 24 | } 25 | 26 | const t = arr[k]; 27 | let i = left; 28 | let j = right; 29 | 30 | swap(arr, left, k); 31 | if (compare(arr[right], t) > 0) swap(arr, left, right); 32 | 33 | while (i < j) { 34 | swap(arr, i, j); 35 | i++; 36 | j--; 37 | while (compare(arr[i], t) < 0) i++; 38 | while (compare(arr[j], t) > 0) j--; 39 | } 40 | 41 | if (compare(arr[left], t) === 0) { 42 | swap(arr, left, j); 43 | } else { 44 | j++; 45 | swap(arr, j, right); 46 | } 47 | 48 | if (j <= k) left = j + 1; 49 | if (k <= j) right = j - 1; 50 | } 51 | } 52 | 53 | function swap(arr: T[], i: number, j: number) { 54 | const tmp = arr[i]; 55 | arr[i] = arr[j]; 56 | arr[j] = tmp; 57 | } 58 | -------------------------------------------------------------------------------- /packages/terra-draw/src/test/create-store-features.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | import { SELECT_PROPERTIES } from "../common"; 3 | import { BehaviorConfig } from "../modes/base.behavior"; 4 | 5 | export const createStoreMidPoint = ( 6 | config: BehaviorConfig, 7 | coordinates: Position = [0, 0], 8 | ) => { 9 | const [createdId] = config.store.create([ 10 | { 11 | geometry: { 12 | type: "Point", 13 | coordinates, 14 | }, 15 | properties: { 16 | [SELECT_PROPERTIES.MID_POINT]: true, 17 | }, 18 | }, 19 | ]); 20 | 21 | return createdId; 22 | }; 23 | 24 | export const createStorePoint = ( 25 | config: BehaviorConfig, 26 | coordinates: Position = [0, 0], 27 | selected = true, 28 | ) => { 29 | const [createdId] = config.store.create([ 30 | { 31 | geometry: { 32 | type: "Point", 33 | coordinates, 34 | }, 35 | properties: { 36 | selected, 37 | }, 38 | }, 39 | ]); 40 | 41 | return createdId; 42 | }; 43 | 44 | export const createStorePolygon = ( 45 | config: BehaviorConfig, 46 | coordinates: Position[][] = [ 47 | [ 48 | [0, 0], 49 | [0, 1], 50 | [1, 1], 51 | [1, 0], 52 | [0, 0], 53 | ], 54 | ], 55 | selected = true, 56 | mode = "test", 57 | ) => { 58 | const [createdId] = config.store.create([ 59 | { 60 | geometry: { 61 | type: "Polygon", 62 | coordinates, 63 | }, 64 | properties: { 65 | selected, 66 | mode, 67 | }, 68 | }, 69 | ]); 70 | 71 | return createdId; 72 | }; 73 | 74 | export const createStoreLineString = ( 75 | config: BehaviorConfig, 76 | coordinates: Position[] = [ 77 | [0, 0], 78 | [0, 1], 79 | ], 80 | selected = true, 81 | ) => { 82 | const [createdId] = config.store.create([ 83 | { 84 | geometry: { 85 | type: "LineString", 86 | coordinates, 87 | }, 88 | properties: { 89 | selected, 90 | }, 91 | }, 92 | ]); 93 | 94 | return createdId; 95 | }; 96 | -------------------------------------------------------------------------------- /packages/terra-draw/src/test/mock-behavior-config.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorConfig } from "../modes/base.behavior"; 2 | import { GeoJSONStore } from "../store/store"; 3 | 4 | export const MockBehaviorConfig = ( 5 | mode: string, 6 | projection?: "web-mercator" | "globe", 7 | ) => 8 | ({ 9 | store: new GeoJSONStore(), 10 | mode, 11 | project: jest.fn((lng, lat) => ({ x: lng * 40, y: lat * 40 })), 12 | unproject: jest.fn((x, y) => ({ lng: x / 40, lat: y / 40 })), 13 | pointerDistance: 40, 14 | coordinatePrecision: 9, 15 | projection: projection ? projection : "web-mercator", 16 | }) as BehaviorConfig; 17 | -------------------------------------------------------------------------------- /packages/terra-draw/src/test/mock-cursor-event.ts: -------------------------------------------------------------------------------- 1 | import { TerraDrawMouseEvent } from "../common"; 2 | 3 | export const MockCursorEvent = ({ 4 | lng, 5 | lat, 6 | button, 7 | isContextMenu, 8 | }: { 9 | lng: TerraDrawMouseEvent["lng"]; 10 | lat: TerraDrawMouseEvent["lat"]; 11 | button?: TerraDrawMouseEvent["button"]; 12 | isContextMenu?: TerraDrawMouseEvent["isContextMenu"]; 13 | }) => 14 | ({ 15 | lng, 16 | lat, 17 | containerX: lng * 40, 18 | containerY: lat * 40, 19 | button: button ? button : ("left" as const), 20 | heldKeys: [], 21 | isContextMenu: isContextMenu ? isContextMenu : false, 22 | }) as TerraDrawMouseEvent; 23 | -------------------------------------------------------------------------------- /packages/terra-draw/src/test/mock-features.ts: -------------------------------------------------------------------------------- 1 | import { Feature, LineString, Point, Polygon } from "geojson"; 2 | 3 | const mockUUID = "29da86c2-92e2-4095-a1b3-22103535ebfa"; 4 | 5 | function createMockFeature( 6 | id: string, 7 | geometry: T, 8 | ): Feature { 9 | return { 10 | id: id ? id : mockUUID, 11 | type: "Feature", 12 | properties: { 13 | mode: geometry.type.toLowerCase(), 14 | }, 15 | geometry: geometry, 16 | }; 17 | } 18 | 19 | export function MockPolygonSquare( 20 | id?: string, 21 | squareStart?: number, 22 | squareEnd?: number, 23 | ): Feature { 24 | squareStart = squareStart !== undefined ? squareStart : 0; 25 | squareEnd = squareEnd !== undefined ? squareEnd : 1; 26 | 27 | return createMockFeature(id || mockUUID, { 28 | type: "Polygon", 29 | coordinates: [ 30 | [ 31 | // 0, 0 0,1------1,1 32 | // 0, 1 | | 33 | // 1, 1 | | 34 | // 1, 0 | | 35 | // 0, 0 . 0,0------1,0 36 | 37 | [squareStart, squareStart], 38 | [squareStart, squareEnd], 39 | [squareEnd, squareEnd], 40 | [squareEnd, squareStart], 41 | [squareStart, squareStart], 42 | ], 43 | ], 44 | }); 45 | } 46 | 47 | export function MockPoint( 48 | id?: string, 49 | lng?: number, 50 | lat?: number, 51 | ): Feature { 52 | return createMockFeature(id || mockUUID, { 53 | type: "Point", 54 | coordinates: [lng ? lng : 0, lat ? lat : 0], 55 | }); 56 | } 57 | 58 | export function MockLineString(id?: string): Feature { 59 | return createMockFeature(id || mockUUID, { 60 | type: "LineString", 61 | coordinates: [ 62 | [0, 0], 63 | [0, 1], 64 | ], 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /packages/terra-draw/src/test/mock-keyboard-event.ts: -------------------------------------------------------------------------------- 1 | import { TerraDrawKeyboardEvent } from "../common"; 2 | 3 | export const MockKeyboardEvent = ({ 4 | key, 5 | heldKeys, 6 | }: { 7 | key: TerraDrawKeyboardEvent["key"]; 8 | heldKeys?: TerraDrawKeyboardEvent["heldKeys"]; 9 | }) => 10 | ({ 11 | key, 12 | preventDefault: jest.fn(), 13 | heldKeys: heldKeys ? heldKeys : [], 14 | }) as TerraDrawKeyboardEvent; 15 | -------------------------------------------------------------------------------- /packages/terra-draw/src/test/mock-mode-config.ts: -------------------------------------------------------------------------------- 1 | import { OnChangeContext } from "../common"; 2 | import { GeoJSONStore } from "../store/store"; 3 | 4 | export function MockModeConfig(mode: string) { 5 | return { 6 | mode, 7 | store: new GeoJSONStore(), 8 | setCursor: jest.fn(), 9 | onChange: jest.fn(), 10 | onSelect: jest.fn(), 11 | onDeselect: jest.fn(), 12 | project: jest.fn((lng, lat) => ({ x: lng * 40, y: lat * 40 })), 13 | unproject: jest.fn((x, y) => ({ lng: x / 40, lat: y / 40 })), 14 | setDoubleClickToZoom: jest.fn(), 15 | onFinish: jest.fn(), 16 | coordinatePrecision: 9, 17 | projection: "web-mercator", 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /packages/terra-draw/src/util/geom.spec.ts: -------------------------------------------------------------------------------- 1 | import { createLineString, createPolygon } from "./geoms"; 2 | 3 | describe("Geom", () => { 4 | describe("createPolygon", () => { 5 | it("creates a polygon", () => { 6 | const polygon = createPolygon([ 7 | [ 8 | [0, 0], 9 | [0, 1], 10 | [1, 1], 11 | [1, 0], 12 | [0, 0], 13 | ], 14 | ]); 15 | expect(polygon).toStrictEqual({ 16 | geometry: { 17 | coordinates: [ 18 | [ 19 | [0, 0], 20 | [0, 1], 21 | [1, 1], 22 | [1, 0], 23 | [0, 0], 24 | ], 25 | ], 26 | type: "Polygon", 27 | }, 28 | properties: {}, 29 | type: "Feature", 30 | }); 31 | }); 32 | 33 | it("creates a polygon with default coordinates", () => { 34 | const polygon = createPolygon(); 35 | expect(polygon).toStrictEqual({ 36 | geometry: { 37 | coordinates: [ 38 | [ 39 | [0, 0], 40 | [0, 1], 41 | [1, 1], 42 | [1, 0], 43 | [0, 0], 44 | ], 45 | ], 46 | type: "Polygon", 47 | }, 48 | properties: {}, 49 | type: "Feature", 50 | }); 51 | }); 52 | }); 53 | 54 | describe("createLineString", () => { 55 | it("creates a linestring", () => { 56 | const linestring = createLineString([ 57 | [0, 0], 58 | [0, 1], 59 | ]); 60 | expect(linestring).toStrictEqual({ 61 | geometry: { 62 | coordinates: [ 63 | [0, 0], 64 | [0, 1], 65 | ], 66 | 67 | type: "LineString", 68 | }, 69 | properties: {}, 70 | type: "Feature", 71 | }); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/terra-draw/src/util/geoms.ts: -------------------------------------------------------------------------------- 1 | import { Feature, LineString, Polygon, Position } from "geojson"; 2 | 3 | export function createPolygon( 4 | coordinates: Position[][] = [ 5 | [ 6 | [0, 0], 7 | [0, 1], 8 | [1, 1], 9 | [1, 0], 10 | [0, 0], 11 | ], 12 | ], 13 | ): Feature { 14 | return { 15 | type: "Feature", 16 | geometry: { 17 | type: "Polygon", 18 | coordinates, 19 | }, 20 | properties: {}, 21 | }; 22 | } 23 | 24 | export function createLineString(coordinates: Position[]): Feature { 25 | return { 26 | type: "Feature", 27 | geometry: { 28 | type: "LineString", 29 | coordinates, 30 | }, 31 | properties: {}, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /packages/terra-draw/src/util/id.spec.ts: -------------------------------------------------------------------------------- 1 | import { uuid4 } from "./id"; 2 | 3 | describe("Util", () => { 4 | describe("uuidv4", () => { 5 | it("generates uuidv4", () => { 6 | const uuid = uuid4(); 7 | expect(typeof uuid).toBe("string"); 8 | expect(uuid.length).toBe(36); 9 | expect(uuid.split("-").length).toBe(5); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/terra-draw/src/util/id.ts: -------------------------------------------------------------------------------- 1 | export const uuid4 = function (): string { 2 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { 3 | const r = (Math.random() * 16) | 0, 4 | v = c == "x" ? r : (r & 0x3) | 0x8; 5 | return v.toString(16); 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /packages/terra-draw/src/util/styling.spec.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultStyling } from "./styling"; 2 | 3 | describe("Styling", () => { 4 | describe("getDefaultStyling", () => { 5 | it("gets valid styles", () => { 6 | const styling = getDefaultStyling(); 7 | expect(styling.pointOutlineColor.startsWith("#")).toBe(true); 8 | expect(styling.pointOutlineColor.length).toBe(7); 9 | 10 | expect(styling.pointColor.startsWith("#")).toBe(true); 11 | expect(styling.pointColor.length).toBe(7); 12 | 13 | expect(styling.polygonOutlineColor.startsWith("#")).toBe(true); 14 | expect(styling.polygonOutlineColor.length).toBe(7); 15 | 16 | expect(styling.polygonFillColor.startsWith("#")).toBe(true); 17 | expect(styling.polygonFillColor.length).toBe(7); 18 | 19 | expect(styling.lineStringColor.startsWith("#")).toBe(true); 20 | expect(styling.lineStringColor.length).toBe(7); 21 | 22 | expect(typeof styling.lineStringWidth).toBe("number"); 23 | expect(typeof styling.pointWidth).toBe("number"); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/terra-draw/src/util/styling.ts: -------------------------------------------------------------------------------- 1 | import { TerraDrawAdapterStyling } from "../common"; 2 | 3 | export const getDefaultStyling = (): TerraDrawAdapterStyling => { 4 | return { 5 | polygonFillColor: "#3f97e0", 6 | polygonOutlineColor: "#3f97e0", 7 | polygonOutlineWidth: 4, 8 | polygonFillOpacity: 0.3, 9 | pointColor: "#3f97e0", 10 | pointOutlineColor: "#ffffff", 11 | pointOutlineWidth: 0, 12 | pointWidth: 6, 13 | lineStringColor: "#3f97e0", 14 | lineStringWidth: 4, 15 | zIndex: 0, 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validation-reasons.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ValidationReasonModeMismatch, 3 | ValidationReasonFeatureNotPolygon, 4 | } from "./validations/common-validations"; 5 | import { ValidationReasonFeatureLessThanMinSize } from "./validations/min-size.validation"; 6 | import { 7 | ValidationReasonFeatureNotPolygonOrLineString, 8 | ValidationReasonFeatureSelfIntersects, 9 | } from "./validations/not-self-intersecting.validation"; 10 | import { 11 | ValidationReasonFeatureInvalidCoordinates, 12 | ValidationReasonFeatureNotPoint, 13 | ValidationReasonFeatureInvalidCoordinatePrecision, 14 | } from "./validations/point.validation"; 15 | import { 16 | ValidationReasonFeatureHasHoles, 17 | ValidationReasonFeatureLessThanFourCoordinates, 18 | ValidationReasonFeatureHasInvalidCoordinates, 19 | ValidationReasonFeatureCoordinatesNotClosed, 20 | } from "./validations/polygon.validation"; 21 | 22 | export const ValidationReasons = { 23 | ValidationReasonFeatureNotPoint, 24 | ValidationReasonFeatureInvalidCoordinates, 25 | ValidationReasonFeatureInvalidCoordinatePrecision, 26 | ValidationReasonFeatureNotPolygon, 27 | ValidationReasonFeatureHasHoles, 28 | ValidationReasonFeatureLessThanFourCoordinates, 29 | ValidationReasonFeatureHasInvalidCoordinates, 30 | ValidationReasonFeatureCoordinatesNotClosed, 31 | ValidationReasonFeatureNotPolygonOrLineString, 32 | ValidationReasonFeatureSelfIntersects, 33 | ValidationReasonFeatureLessThanMinSize, 34 | ValidationReasonModeMismatch, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/common-validations.ts: -------------------------------------------------------------------------------- 1 | export const ValidationReasonFeatureNotPolygon = "Feature is not a Polygon"; 2 | export const ValidationReasonModeMismatch = 3 | "Feature mode property does not match the mode being added to"; 4 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/linestring.validation.spec.ts: -------------------------------------------------------------------------------- 1 | import { Feature, LineString } from "geojson"; 2 | import { ValidateLineStringFeature } from "./linestring.validation"; 3 | 4 | describe("ValidateLineStringFeature", () => { 5 | it("returns true for a valid LineString feature with correct coordinate precision", () => { 6 | const validFeature = { 7 | type: "Feature", 8 | properties: {}, 9 | geometry: { 10 | type: "LineString", 11 | coordinates: [ 12 | [45, 80], 13 | [46, 81], 14 | ], 15 | }, 16 | } as Feature>; 17 | expect(ValidateLineStringFeature(validFeature, 9)).toEqual({ valid: true }); 18 | }); 19 | 20 | it("returns false for a non-LineString feature", () => { 21 | const nonLineStringFeature = { 22 | type: "Feature", 23 | properties: {}, 24 | geometry: { 25 | type: "Polygon", 26 | coordinates: [ 27 | [45, 80], 28 | [46, 81], 29 | [45, 80], 30 | ], 31 | }, 32 | } as any; 33 | expect(ValidateLineStringFeature(nonLineStringFeature, 9)).toEqual({ 34 | valid: false, 35 | reason: "Feature is not a LineString", 36 | }); 37 | }); 38 | 39 | it("returns false for a LineString feature with less than 2 coordinates", () => { 40 | const lessCoordinatesFeature = { 41 | type: "Feature", 42 | properties: {}, 43 | geometry: { 44 | type: "LineString", 45 | coordinates: [[45, 90]], 46 | }, 47 | } as Feature>; 48 | expect(ValidateLineStringFeature(lessCoordinatesFeature, 9)).toEqual({ 49 | valid: false, 50 | reason: "Feature has less than 2 coordinates", 51 | }); 52 | }); 53 | 54 | it("returns false for a LineString feature with incorrect coordinate precision", () => { 55 | const validFeature = { 56 | type: "Feature", 57 | properties: {}, 58 | geometry: { 59 | type: "LineString", 60 | coordinates: [ 61 | [45.123, 80.123], 62 | [46.123, 81.123], 63 | ], 64 | }, 65 | } as Feature>; 66 | expect(ValidateLineStringFeature(validFeature, 2)).toEqual({ 67 | valid: false, 68 | reason: "Feature has coordinates with excessive precision", 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/linestring.validation.ts: -------------------------------------------------------------------------------- 1 | import { Validation } from "../common"; 2 | import { GeoJSONStoreFeatures } from "../terra-draw"; 3 | import { 4 | coordinateIsValid, 5 | coordinatePrecisionIsValid, 6 | } from "../geometry/boolean/is-valid-coordinate"; 7 | 8 | export const ValidationReasonFeatureIsNotALineString = 9 | "Feature is not a LineString"; 10 | export const ValidationReasonFeatureHasLessThanTwoCoordinates = 11 | "Feature has less than 2 coordinates"; 12 | export const ValidationReasonFeatureInvalidCoordinates = 13 | "Feature has invalid coordinates"; 14 | export const ValidationReasonFeatureInvalidCoordinatePrecision = 15 | "Feature has coordinates with excessive precision"; 16 | 17 | export function ValidateLineStringFeature( 18 | feature: GeoJSONStoreFeatures, 19 | coordinatePrecision: number, 20 | ): ReturnType { 21 | if (feature.geometry.type !== "LineString") { 22 | return { 23 | valid: false, 24 | reason: ValidationReasonFeatureIsNotALineString, 25 | }; 26 | } 27 | 28 | if (feature.geometry.coordinates.length < 2) { 29 | return { 30 | valid: false, 31 | reason: ValidationReasonFeatureHasLessThanTwoCoordinates, 32 | }; 33 | } 34 | 35 | for (let i = 0; i < feature.geometry.coordinates.length; i++) { 36 | if (!coordinateIsValid(feature.geometry.coordinates[i])) { 37 | return { 38 | valid: false, 39 | reason: ValidationReasonFeatureInvalidCoordinates, 40 | }; 41 | } 42 | 43 | if ( 44 | !coordinatePrecisionIsValid( 45 | feature.geometry.coordinates[i], 46 | coordinatePrecision, 47 | ) 48 | ) { 49 | return { 50 | valid: false, 51 | reason: ValidationReasonFeatureInvalidCoordinatePrecision, 52 | }; 53 | } 54 | } 55 | 56 | return { valid: true }; 57 | } 58 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/max-size.validation.spec.ts: -------------------------------------------------------------------------------- 1 | import { ValidateMaxAreaSquareMeters } from "./max-size.validation"; 2 | import { GeoJSONStoreFeatures } from "../terra-draw"; 3 | 4 | describe("ValidateMaxAreaSquareMeters", () => { 5 | it("should return true if the polygon area is less than the max size", () => { 6 | // Arrange 7 | const polygon = { 8 | type: "Feature", 9 | properties: {}, 10 | geometry: { 11 | type: "Polygon", 12 | coordinates: [ 13 | [ 14 | [0, 0], 15 | [0, 1], 16 | [1, 1], 17 | [1, 0], 18 | [0, 0], 19 | ], 20 | ], 21 | }, 22 | } as GeoJSONStoreFeatures; 23 | 24 | const maxSize = 100000000000; 25 | 26 | const result = ValidateMaxAreaSquareMeters(polygon, maxSize); 27 | expect(result).toEqual({ valid: true }); 28 | }); 29 | 30 | it("should return true if the polygon area is less than the max size", () => { 31 | // Arrange 32 | const polygon = { 33 | type: "Feature", 34 | properties: {}, 35 | geometry: { 36 | type: "Polygon", 37 | coordinates: [ 38 | [ 39 | [0, 0], 40 | [0, 1], 41 | [1, 1], 42 | [1, 0], 43 | [0, 0], 44 | ], 45 | ], 46 | }, 47 | } as GeoJSONStoreFeatures; 48 | 49 | const maxSize = 1000000; 50 | 51 | const result = ValidateMaxAreaSquareMeters(polygon, maxSize); 52 | expect(result).toEqual({ 53 | valid: false, 54 | reason: "Feature is larger than the maximum area", 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/max-size.validation.ts: -------------------------------------------------------------------------------- 1 | import { Validation } from "../common"; 2 | import { polygonAreaSquareMeters } from "../geometry/measure/area"; 3 | import { GeoJSONStoreFeatures } from "../terra-draw"; 4 | import { ValidationReasonFeatureNotPolygon } from "./common-validations"; 5 | 6 | export const ValidationMaxAreaSquareMetersReason = 7 | "Feature is larger than the maximum area"; 8 | 9 | export const ValidateMaxAreaSquareMeters = ( 10 | feature: GeoJSONStoreFeatures, 11 | maxSize: number, 12 | ): ReturnType => { 13 | if (feature.geometry.type !== "Polygon") { 14 | return { 15 | valid: false, 16 | reason: ValidationReasonFeatureNotPolygon, 17 | }; 18 | } 19 | 20 | const size = polygonAreaSquareMeters(feature.geometry); 21 | 22 | if (size > maxSize) { 23 | return { 24 | valid: false, 25 | reason: ValidationMaxAreaSquareMetersReason, 26 | }; 27 | } 28 | 29 | return { valid: true }; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/min-size.validation.spec.ts: -------------------------------------------------------------------------------- 1 | import { Polygon } from "geojson"; 2 | import { ValidateMinAreaSquareMeters } from "./min-size.validation"; 3 | import { GeoJSONStoreFeatures } from "../terra-draw"; 4 | 5 | describe("ValidateMinAreaSquareMeters", () => { 6 | it("it should return true if less than the min size provided", () => { 7 | // Arrange 8 | const feature = { 9 | type: "Feature", 10 | geometry: { 11 | type: "Polygon", 12 | coordinates: [ 13 | [ 14 | [0, 0], 15 | [0, 1], 16 | [1, 1], 17 | [1, 0], 18 | [0, 0], 19 | ], 20 | ], 21 | } as Polygon, 22 | } as GeoJSONStoreFeatures; 23 | 24 | const minSize = 10000; 25 | const result = ValidateMinAreaSquareMeters(feature, minSize); 26 | expect(result).toEqual({ valid: true }); 27 | }); 28 | 29 | it("it should return false if less than the min size provided", () => { 30 | // Arrange 31 | const feature = { 32 | type: "Feature", 33 | geometry: { 34 | type: "Polygon", 35 | coordinates: [ 36 | [ 37 | [0, 0], 38 | [0, 1], 39 | [1, 1], 40 | [1, 0], 41 | [0, 0], 42 | ], 43 | ], 44 | } as Polygon, 45 | } as GeoJSONStoreFeatures; 46 | 47 | const minSize = 100000000000; 48 | const result = ValidateMinAreaSquareMeters(feature, minSize); 49 | expect(result).toEqual({ 50 | valid: false, 51 | reason: "Feature is smaller than the minimum area", 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/min-size.validation.ts: -------------------------------------------------------------------------------- 1 | import { Validation } from "../common"; 2 | import { polygonAreaSquareMeters } from "../geometry/measure/area"; 3 | import { GeoJSONStoreFeatures } from "../terra-draw"; 4 | import { ValidationReasonFeatureNotPolygon } from "./common-validations"; 5 | 6 | export const ValidationReasonFeatureLessThanMinSize = 7 | "Feature is smaller than the minimum area"; 8 | 9 | export const ValidateMinAreaSquareMeters = ( 10 | feature: GeoJSONStoreFeatures, 11 | minSize: number, 12 | ): ReturnType => { 13 | if (feature.geometry.type !== "Polygon") { 14 | return { 15 | valid: false, 16 | reason: ValidationReasonFeatureNotPolygon, 17 | }; 18 | } 19 | 20 | if (polygonAreaSquareMeters(feature.geometry) < minSize) { 21 | return { 22 | valid: false, 23 | reason: ValidationReasonFeatureLessThanMinSize, 24 | }; 25 | } 26 | 27 | return { valid: true }; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/not-self-intersecting.validation.spec.ts: -------------------------------------------------------------------------------- 1 | import { Polygon } from "geojson"; 2 | import { ValidateNotSelfIntersecting } from "./not-self-intersecting.validation"; 3 | import { GeoJSONStoreFeatures } from "../terra-draw"; 4 | 5 | describe("ValidateNotSelfIntersecting", () => { 6 | it("it should return false polygon self intersects", () => { 7 | const feature = { 8 | type: "Feature", 9 | properties: {}, 10 | geometry: { 11 | coordinates: [ 12 | [ 13 | [34.22041933638323, 16.155508656132255], 14 | [14.634103624858, 1.9928911643832947], 15 | [44.85353434697225, -10.538516452804046], 16 | [15.981035610620069, 13.843769534036511], 17 | [34.22041933638323, 16.155508656132255], 18 | ], 19 | ], 20 | type: "Polygon", 21 | }, 22 | } as GeoJSONStoreFeatures; 23 | 24 | const result = ValidateNotSelfIntersecting(feature); 25 | expect(result).toEqual({ 26 | valid: false, 27 | reason: "Feature intersects itself", 28 | }); 29 | }); 30 | 31 | it("it should return true if polygon does not self intersects", () => { 32 | const feature = { 33 | type: "Feature", 34 | geometry: { 35 | coordinates: [ 36 | [ 37 | [22.115939239949142, 17.061864550222168], 38 | [10.555809858425931, 17.061864550222168], 39 | [10.555809858425931, 4.004764588790522], 40 | [22.115939239949142, 4.004764588790522], 41 | [22.115939239949142, 17.061864550222168], 42 | ], 43 | ], 44 | type: "Polygon", 45 | } as Polygon, 46 | } as GeoJSONStoreFeatures; 47 | 48 | const result = ValidateNotSelfIntersecting(feature); 49 | expect(result).toEqual({ valid: true }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/not-self-intersecting.validation.ts: -------------------------------------------------------------------------------- 1 | import { Feature, LineString, Polygon } from "geojson"; 2 | import { selfIntersects } from "../geometry/boolean/self-intersects"; 3 | import { GeoJSONStoreFeatures } from "../terra-draw"; 4 | import { Validation } from "../common"; 5 | 6 | export const ValidationReasonFeatureNotPolygonOrLineString = 7 | "Feature is not a Polygon or LineString"; 8 | export const ValidationReasonFeatureSelfIntersects = 9 | "Feature intersects itself"; 10 | 11 | export const ValidateNotSelfIntersecting = ( 12 | feature: GeoJSONStoreFeatures, 13 | ): ReturnType => { 14 | if ( 15 | feature.geometry.type !== "Polygon" && 16 | feature.geometry.type !== "LineString" 17 | ) { 18 | return { 19 | valid: false, 20 | reason: ValidationReasonFeatureNotPolygonOrLineString, 21 | }; 22 | } 23 | 24 | const hasSelfIntersections = selfIntersects( 25 | feature as Feature | Feature, 26 | ); 27 | 28 | if (hasSelfIntersections) { 29 | return { 30 | valid: false, 31 | reason: ValidationReasonFeatureSelfIntersects, 32 | }; 33 | } 34 | 35 | return { valid: true }; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/point.validation.spec.ts: -------------------------------------------------------------------------------- 1 | import { Feature, Point } from "geojson"; 2 | import { ValidatePointFeature } from "./point.validation"; 3 | 4 | describe("isValidPoint", () => { 5 | it("returns true for a valid Point with correct coordinate precision", () => { 6 | const validPoint = { 7 | type: "Feature", 8 | properties: {}, 9 | geometry: { 10 | type: "Point", 11 | coordinates: [45, 90], 12 | }, 13 | } as Feature>; 14 | expect(ValidatePointFeature(validPoint, 2)).toEqual({ valid: true }); 15 | }); 16 | 17 | it("returns false for a non-Point feature", () => { 18 | const nonPointFeature = { 19 | type: "Feature", 20 | properties: {}, 21 | geometry: { 22 | type: "LineString", 23 | coordinates: [ 24 | [45, 90], 25 | [46, 91], 26 | ], 27 | }, 28 | } as any; 29 | expect(ValidatePointFeature(nonPointFeature, 2)).toEqual({ 30 | valid: false, 31 | reason: "Feature is not a Point", 32 | }); 33 | }); 34 | 35 | it("returns false for a Point with invalid latitude", () => { 36 | const invalidPoint = { 37 | type: "Feature", 38 | properties: {}, 39 | geometry: { 40 | type: "Point", 41 | coordinates: [45.123, 90.123], 42 | }, 43 | } as Feature>; 44 | expect(ValidatePointFeature(invalidPoint, 9)).toEqual({ 45 | valid: false, 46 | reason: "Feature has invalid coordinates", 47 | }); 48 | }); 49 | 50 | it("returns false for a Point with incorrect coordinate precision", () => { 51 | const invalidPoint = { 52 | type: "Feature", 53 | properties: {}, 54 | geometry: { 55 | type: "Point", 56 | coordinates: [45.123, 89.123], 57 | }, 58 | } as Feature>; 59 | expect(ValidatePointFeature(invalidPoint, 2)).toEqual({ 60 | valid: false, 61 | reason: "Feature has coordinates with excessive precision", 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/terra-draw/src/validations/point.validation.ts: -------------------------------------------------------------------------------- 1 | import { Validation } from "../common"; 2 | import { GeoJSONStoreFeatures } from "../terra-draw"; 3 | import { 4 | coordinateIsValid, 5 | coordinatePrecisionIsValid, 6 | } from "../geometry/boolean/is-valid-coordinate"; 7 | 8 | export const ValidationReasonFeatureNotPoint = "Feature is not a Point"; 9 | export const ValidationReasonFeatureInvalidCoordinates = 10 | "Feature has invalid coordinates"; 11 | export const ValidationReasonFeatureInvalidCoordinatePrecision = 12 | "Feature has coordinates with excessive precision"; 13 | 14 | export function ValidatePointFeature( 15 | feature: GeoJSONStoreFeatures, 16 | coordinatePrecision: number, 17 | ): ReturnType { 18 | if (feature.geometry.type !== "Point") { 19 | return { 20 | valid: false, 21 | reason: ValidationReasonFeatureNotPoint, 22 | }; 23 | } 24 | 25 | if (!coordinateIsValid(feature.geometry.coordinates)) { 26 | return { 27 | valid: false, 28 | reason: ValidationReasonFeatureInvalidCoordinates, 29 | }; 30 | } 31 | 32 | if ( 33 | !coordinatePrecisionIsValid( 34 | feature.geometry.coordinates, 35 | coordinatePrecision, 36 | ) 37 | ) { 38 | return { 39 | valid: false, 40 | reason: ValidationReasonFeatureInvalidCoordinatePrecision, 41 | }; 42 | } 43 | 44 | return { valid: true }; 45 | } 46 | -------------------------------------------------------------------------------- /packages/terra-draw/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../../tsconfig.base.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "dist", 7 | "rootDir": "src" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /release.js: -------------------------------------------------------------------------------- 1 | module.exports = (packageName, packageJsonPath, changelogPath) => ({ 2 | bumpFiles: [ 3 | { 4 | filename: packageJsonPath, 5 | type: "json", 6 | }, 7 | ], 8 | packageFiles: [packageJsonPath], 9 | writerOpts: { 10 | transform: (commit, context) => { 11 | // Only include commits scoped to the package 12 | if (!commit.scope || commit.scope !== packageName) { 13 | return null; 14 | } 15 | 16 | // If commit message starts with automated update 17 | if (commit.header.includes("automated update")) { 18 | return null; 19 | } 20 | 21 | return commit; 22 | }, 23 | }, 24 | changelogFile: changelogPath, 25 | releaseCommitMessageFormat: `chore(${packageName}): release version {{currentTag}}`, 26 | }); 27 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "strict": true, 5 | "module": "es6", 6 | "target": "es6", 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "moduleResolution": "node", 11 | "allowSyntheticDefaultImports": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "files": [], 4 | "references": [ 5 | { 6 | "path": "./packages/terra-draw" 7 | }, 8 | { 9 | "path": "./packages/terra-draw-arcgis-adapter" 10 | }, 11 | { 12 | "path": "./packages/terra-draw-google-maps-adapter" 13 | }, 14 | { 15 | "path": "./packages/terra-draw-leaflet-adapter" 16 | }, 17 | { 18 | "path": "./packages/terra-draw-mapbox-gl-adapter" 19 | }, 20 | { 21 | "path": "./packages/terra-draw-maplibre-gl-adapter" 22 | }, 23 | { 24 | "path": "./packages/terra-draw-openlayers-adapter" 25 | } 26 | ], 27 | "typedocOptions": { 28 | "entryPoints": [ 29 | "packages/terra-draw/src/terra-draw.ts", 30 | "packages/terra-draw-arcgis-adapter/src/terra-draw-arcgis-adapter.ts", 31 | "packages/terra-draw-google-maps-adapter/src/terra-draw-google-maps-adapter.ts", 32 | "packages/terra-draw-leaflet-adapter/src/terra-draw-leaflet-adapter.ts", 33 | "packages/terra-draw-mapbox-gl-adapter/src/terra-draw-mapbox-gl-adapter.ts", 34 | "packages/terra-draw-maplibre-gl-adapter/src/terra-draw-maplibre-gl-adapter.ts", 35 | "packages/terra-draw-openlayers-adapter/src/terra-draw-openlayers-adapter.ts" 36 | ], 37 | "excludeExternals": true, 38 | "exclude": [ 39 | "packages/development/", 40 | "packages/e2e", 41 | "packages/terra-draw/src/geometry/**/*.ts", 42 | "packages/terra-draw/src/test/**/*.ts", 43 | "packages/terra-draw/src/util/**/*.ts" 44 | ], 45 | "out": "docs", 46 | "skipErrorChecking": true, 47 | "sourceLinkExternal": true 48 | } 49 | } 50 | --------------------------------------------------------------------------------