├── .github └── workflows │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.json ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── arcgis │ ├── README.hbs │ ├── README.md │ ├── package.json │ ├── src │ │ ├── arcgis.js │ │ ├── geojson.js │ │ ├── helpers.js │ │ └── index.js │ └── test │ │ ├── arcgis.test.js │ │ └── geojson.test.js ├── common │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ └── index.test.js ├── spatial │ ├── README.hbs │ ├── README.md │ ├── package.json │ ├── src │ │ ├── bounds.js │ │ ├── circle.js │ │ ├── constants.js │ │ ├── contains.js │ │ ├── convex.js │ │ ├── index.js │ │ ├── intersects.js │ │ ├── polygon.js │ │ ├── position.js │ │ ├── util.js │ │ └── within.js │ └── test │ │ ├── crs.test.js │ │ ├── mock │ │ └── geojson.js │ │ └── spatial.test.js └── wkt │ ├── README.hbs │ ├── README.md │ ├── build-esm.js │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── wkt.js │ └── wkt.yy │ └── test │ ├── geojson.test.js │ └── wkt.test.js ├── rollup.esm.config.js ├── rollup.umd.config.js └── test-helper.js /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | node: [16, 18, 20] 17 | 18 | steps: 19 | - name: Reconfigure git to use HTTP authentication 20 | uses: actions/checkout@v3 21 | - run: > 22 | git config --global url."https://github.com/".insteadOf 23 | ssh://git@github.com/ 24 | - name: Use Node.js ${{ matrix.node }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node }} 28 | - run: npm i 29 | - run: npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # Rollup Typescript caches 62 | .rpt2_cache 63 | 64 | # Lerna files 65 | lerna-debug.log* 66 | 67 | # Garbage 68 | .DS_Store 69 | 70 | # docs temp files 71 | docs/.typedoc_temp/ 72 | 73 | # Built files 74 | dist 75 | .zip 76 | 77 | # Debugging files 78 | packages/*/debug/ 79 | 80 | # no idea 81 | /packages/*/.rpt2_cache 82 | 83 | # packages in development 84 | packages/arcgis-rest-portal/ 85 | 86 | # 87 | test.html 88 | test.js 89 | 90 | # 91 | demos/test/* 92 | 93 | # test log 94 | packages/**/.source.**.html 95 | 96 | # jsdoc 97 | docs/* 98 | 99 | # created by jison 100 | packages/wkt/src/index.js 101 | 102 | # 103 | .vscode/ 104 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [Unreleased] 7 | 8 | ## [2.2.1] - 2024-05-15 9 | 10 | ### Added 11 | * `@terraformer/wkt` 12 | * Errors parsing WKT are now surfaced instead of swallowed. 🙏🏽 @mpalmerlee 🙏🏽 13 | 14 | ## [2.2.0] - 2023-03-28 15 | 16 | ### Added 17 | * `@terraformer/wkt` 18 | * Parse WKT Geometry Collection to GeoJSON Geomtery Collection. 🙏🏽 @Zakhmaster 🙏🏽 19 | 20 | ## [2.1.2] - 2022-08-02 21 | ### Fixed 22 | 23 | * `@terraformer/spatial` 24 | * fix `intersects` for multipolygons 25 | * start exporting `applyConverter` 26 | ## [2.1.1] - 2022-05-31 27 | 28 | ### Fixed 29 | 30 | * ensure Z values of 0 are carried through when converting GeoJSON to ArcGIS Geometries. 31 | 32 | ## [2.1.0] - 2021-07-22 33 | 34 | ### Added 35 | 36 | * added support for Z values when converting GeoJSON to ArcGIS Geometries. 37 | 38 | ## [2.0.7] - 2020-05-18 39 | 40 | ### Fixed 41 | 42 | * all UMD and ESM files are now transpiled to make them safe to use in older browsers. 43 | 44 | ## [2.0.5] - 2020-05-17 45 | 46 | ### Fixed 47 | 48 | * `@terraformer/spatial` 49 | * `toCircle()` now returns polygons of equal area, regardless of their latitude 50 | 51 | ## 2.0.0 - 2020-04-15 52 | 53 | ### Changed 54 | 55 | * New Package names: 56 | * `terraformer` > `@terraformer/spatial`. 57 | * `terraformer-arcgis-parser` > `@terraformer/arcgis`. 58 | * `terraformer-wkt-parser` > `@terraformer/wkt`. 59 | 60 | * All packages are now standalone. 61 | 62 | [2.2.1]: https://github.com/terraformer-js/terraformer/compare/v2.2.0...v2.2.1 63 | [2.2.0]: https://github.com/terraformer-js/terraformer/compare/v2.1.2...v2.2.0 64 | [2.1.2]: https://github.com/terraformer-js/terraformer/compare/v2.1.1...v2.1.2 65 | [2.1.1]: https://github.com/terraformer-js/terraformer/compare/v2.1.0...v2.1.1 66 | [2.1.0]: https://github.com/terraformer-js/terraformer/compare/v2.0.7...v2.1.0 67 | [2.0.7]: https://github.com/terraformer-js/terraformer/compare/v2.0.5...v2.0.7 68 | [2.0.5]: https://github.com/terraformer-js/terraformer/compare/v2.0.0...v2.0.5 69 | [Unreleased]: https://github.com/terraformer-js/terraformer/compare/v2.2.0...HEAD 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 2 | 3 | ## Publishing a release 4 | 5 | 1. Update CHANGELOG.md manually 6 | 1. `npm test` to ensure all is well 7 | 1. `npm run doc` shouldn't result in any changed files 8 | 1. `npm run release` 9 | 10 | under the hood, this will call `lerna publish`. you'll get a chance to specify `patch | minor | major` and only the packages that have changed since last time will be published. 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2019 Environmental Systems Research Institute, Inc 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @terraformer 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | [![standard][standard-image]][standard-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/@terraformer/arcgis.svg?style=flat-square 8 | [npm-url]: https://www.npmjs.com/package/@terraformer/arcgis 9 | [travis-image]: https://app.travis-ci.com/terraformer-js/terraformer.svg?branch=main 10 | [travis-url]: https://app.travis-ci.com/terraformer-js/terraformer 11 | [standard-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square 12 | [standard-url]: http://npm.im/semistandard 13 | 14 | > A geographic toolkit for dealing with geometry, geography, formats, and building geodatabases. 15 | 16 | ## Packages 17 | 18 | * [`@terraformer/spatial`](./packages/spatial/README.md) - Spatial predicates for [GeoJSON](https://tools.ietf.org/html/rfc7946). 19 | * [`@terraformer/arcgis`](./packages/arcgis/README.md) - Convert ArcGIS JSON geometries to GeoJSON geometries and vice versa. 20 | * [`@terraformer/wkt`](./packages/wkt/README.md) - Convert WKT geometries to GeoJSON geometries and vice versa. 21 | 22 | ## FAQ 23 | 24 |
25 | What's the difference between this and Esri/Terraformer? 26 | 27 | Very little! 28 | 29 | This project is a standalone [ES Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) port of the original Terraformer project _without_ the [Primitives](https://terraformer-js.github.io/core/#terraformerprimitive). 30 | 31 | If you found instantiating Primitives tedious or you'd like to [cut down on your bundle size](https://github.com/zakjan/leaflet-lasso/issues/10) by importing only the code from Terraformer that you're actually using, you should consider upgrading. 32 |
33 | 34 |
35 | I'm already using terraformer. How do I upgrade? 36 | 37 | Previously it was necessary to instantiate a Terraformer Primitive in order to execute spatial operations 38 | ```bash 39 | npm install terraformer 40 | ``` 41 | 42 | ```js 43 | const Terraformer = require('terraformer') 44 | 45 | const polygon = new Terraformer.Primitive({ 46 | type: "LineString", 47 | coordinates: [ 48 | [ 100, 0 ], [ -45, 122 ], [ 80, -60 ] 49 | ] 50 | }) 51 | 52 | polygon.convexHull() 53 | ``` 54 | 55 | Now you'll work directly with raw [GeoJSON](https://tools.ietf.org/html/rfc7946) 56 | ``` 57 | npm install @terraformer/spatial 58 | ``` 59 | ```js 60 | import { convexHull } from '@terraformer/spatial' 61 | 62 | convexHull({ 63 | type: "LineString", 64 | coordinates: [ 65 | [ 100, 0 ], [ -45, 122 ], [ 80, -60 ] 66 | ] 67 | }) 68 | ``` 69 | 70 |
71 | 72 |
73 | I'm already using terraformer-wkt-parser. How do I upgrade? 74 | 75 | Instead of this: 76 | ```bash 77 | npm install terraformer-wkt-parser 78 | ``` 79 | 80 | ```js 81 | var wkt = require('terraformer-wkt-parser') 82 | 83 | // parse a WKT file and turn it into GeoJSON 84 | wkt.parse('LINESTRING (30 10, 10 30, 40 40)') 85 | wkt.convert(/* ... */) 86 | ``` 87 | 88 | You'll do this: 89 | ``` 90 | npm install @terraformer/wkt 91 | ``` 92 | ```js 93 | import { wktToGeoJSON, geojsonToWkt } from '@terraformer/wkt' 94 | 95 | wktToGeoJSON(/* ... */) 96 | geojsonToWKT(/* ... */) 97 | ``` 98 |
99 | 100 |
101 | I'm already using terraformer-arcgis-parser. How do I upgrade? 102 | 103 | Instead of this: 104 | ```bash 105 | npm install terraformer-arcgis-parser 106 | ``` 107 | 108 | ```js 109 | var ArcGIS = require('terraformer-arcgis-parser') 110 | 111 | // parse ArcGIS JSON and turn it into GeoJSON 112 | ArcGIS.parse() 113 | ArcGIS.convert() 114 | ``` 115 | 116 | You'll do this: 117 | ``` 118 | npm install @terraformer/arcgis 119 | ``` 120 | ```js 121 | const { arcgisToGeoJSON, geojsonToArcGIS } from '@terraformer/arcgis' 122 | 123 | arcgisToGeoJSON(/* ... */) 124 | geojsonToArcGIS(/* ... */) 125 | ``` 126 |
127 | 128 |
129 | What about terraformer-geostore? 130 | 131 | This repo does **not** include a port of https://github.com/Esri/terraformer-geostore and there is no plan to tackle it in the future. 132 | 133 | Since terraformer-geostore ingests plain ol' [GeoJSON](https://tools.ietf.org/html/rfc7946), you're welcome to keep on using the original code. 134 |
135 | 136 | ## Contributing 137 | 138 | ```shell 139 | npm install && npm test 140 | ``` 141 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", {"modules": false}] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "2.2.1", 6 | "lerna": "3.20.2" 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terraformer", 3 | "private": true, 4 | "version": "2.0.0", 5 | "description": "A geographic toolkit for dealing with geometry, geography, formats, and building geodatabases", 6 | "main": "index.js", 7 | "scripts": { 8 | "bootstrap": "lerna bootstrap", 9 | "build": "lerna run build", 10 | "doc": "lerna run doc", 11 | "docs:serve": "npm run jsdoc && http-server & onchange -v 'packages/**/*.js' -- npm run jsdoc", 12 | "lint": "semistandard | snazzy", 13 | "test:node": "node test-helper.js", 14 | "test:electron": "browserify packages/**/test/*.js -t [ babelify --presets [ @babel/preset-env ] ] --debug | tape-run", 15 | "test:chrome": "browserify packages/**/test/*.js -t [ babelify --presets [ @babel/preset-env ] ] --debug | tape-run --browser chrome", 16 | "test:firefox": "browserify packages/**/test/*.js -t [ babelify --presets [ @babel/preset-env ] ] --debug | tape-run --browser firefox", 17 | "pretest": "npm run build", 18 | "pretest:ci": "npm run build", 19 | "test": "npm run lint && npm run test:node", 20 | "test:ci": "npm run lint && npm run test:node && npm run test:electron", 21 | "prerelease": "npm run build && npm run doc", 22 | "release": "lerna publish", 23 | "postinstall": "npm run bootstrap" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/terraformer-js/terraformer.git" 28 | }, 29 | "keywords": [ 30 | "esri", 31 | "geojson", 32 | "wkt", 33 | "spatial" 34 | ], 35 | "author": "john gravois", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/terraformer-js/terraformer/issues" 39 | }, 40 | "homepage": "https://github.com/terraformer-js/terraformer#readme", 41 | "devDependencies": { 42 | "@babel/cli": "^7.2.3", 43 | "@babel/core": "^7.9.0", 44 | "@babel/node": "^7.2.2", 45 | "@babel/preset-env": "^7.9.5", 46 | "@rollup/plugin-babel": "^5.0.0", 47 | "@rollup/plugin-json": "^4.1.0", 48 | "@rollup/plugin-node-resolve": "^13.3.0", 49 | "babelify": "^10.0.0", 50 | "browserify": "^16.0.0", 51 | "jsdoc": "^4.0.3", 52 | "jsdoc-to-markdown": "^7.1.1", 53 | "lerna": "^5.0.0", 54 | "onchange": "^6.1.0", 55 | "rollup": "^2.77.2", 56 | "semistandard": "^14.2.0", 57 | "snazzy": "^8.0.0", 58 | "tape": "^4.0.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/arcgis/README.hbs: -------------------------------------------------------------------------------- 1 | # @terraformer/arcgis 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | [![standard][standard-image]][standard-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/@terraformer/arcgis.svg?style=flat-square 8 | [npm-url]: https://www.npmjs.com/package/@terraformer/arcgis 9 | [travis-image]: https://app.travis-ci.com/terraformer-js/terraformer.svg?branch=main 10 | [travis-url]: https://app.travis-ci.com/terraformer-js/terraformer 11 | [standard-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square 12 | [standard-url]: http://npm.im/semistandard 13 | 14 | > Convert ArcGIS JSON geometries to GeoJSON geometries and vice versa. 15 | 16 | ## Install 17 | 18 | ```shell 19 | npm install @terraformer/arcgis 20 | ``` 21 | 22 | ## API Reference 23 | 24 | {{>main}} 25 | * * * 26 | 27 | ## Usage 28 | 29 | ### Browser (from CDN) 30 | 31 | This package is distributed as a [UMD](https://github.com/umdjs/umd) module and can also be used in AMD based systems or as a global under the `Terraformer` namespace. 32 | 33 | ```html 34 | 35 | ``` 36 | ```js 37 | Terraformer.arcgisToGeoJSON(/* ... */); 38 | ``` 39 | 40 | ### Node.js 41 | 42 | ```js 43 | const Terraformer = require('@terraformer/arcgis'); 44 | 45 | Terraformer.geojsonToArcGIS(/* ... */); 46 | ``` 47 | 48 | ### ES module in the browser 49 | 50 | ```html 51 | 57 | ``` 58 | 59 | ## [Contributing](./CONTRIBUTING.md) 60 | 61 | ## TypeScript 62 | 63 | Type definitions for `@terraformer/arcgis` can be found at [@types/terraformer__arcgis](https://www.npmjs.com/package/@types/terraformer__arcgis). To install into your own project: 64 | 65 | ``` 66 | npm install @types/terraformer__arcgis 67 | ``` 68 | 69 | ## Ports 70 | 71 | | Project | Language | Status | Maintainer | 72 | | - | - | - | - | 73 | | [`arcgis2geojson`](https://github.com/chris48s/arcgis2geojson/) | Python | Incomplete | [@chris48s](https://github.com/chris48s) | 74 | 75 | ## [LICENSE](https://raw.githubusercontent.com/terraformer-js/terraformer/master/LICENSE) 76 | -------------------------------------------------------------------------------- /packages/arcgis/README.md: -------------------------------------------------------------------------------- 1 | # @terraformer/arcgis 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | [![standard][standard-image]][standard-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/@terraformer/arcgis.svg?style=flat-square 8 | [npm-url]: https://www.npmjs.com/package/@terraformer/arcgis 9 | [travis-image]: https://app.travis-ci.com/terraformer-js/terraformer.svg?branch=main 10 | [travis-url]: https://app.travis-ci.com/terraformer-js/terraformer 11 | [standard-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square 12 | [standard-url]: http://npm.im/semistandard 13 | 14 | > Convert ArcGIS JSON geometries to GeoJSON geometries and vice versa. 15 | 16 | ## Install 17 | 18 | ```shell 19 | npm install @terraformer/arcgis 20 | ``` 21 | 22 | ## API Reference 23 | 24 | 25 | 26 | ## Terraformer 27 | 28 | * [Terraformer](#module_Terraformer) 29 | * [.arcgisToGeoJSON(JSON, [idAttribute])](#module_Terraformer.arcgisToGeoJSON) ⇒ object 30 | * [.geojsonToArcGIS(GeoJSON, [idAttribute])](#module_Terraformer.geojsonToArcGIS) ⇒ object 31 | 32 | 33 | 34 | ### Terraformer.arcgisToGeoJSON(JSON, [idAttribute]) ⇒ object 35 | Converts [ArcGIS JSON](https://developers.arcgis.com/documentation/core-concepts/features-and-geometries/) into GeoJSON. 36 | 37 | **Kind**: static method of [Terraformer](#module_Terraformer) 38 | **Returns**: object - GeoJSON. 39 | ```js 40 | import { arcgisToGeoJSON } from "@terraformer/arcgis" 41 | 42 | arcgisToGeoJSON({ 43 | "x":-122.6764, 44 | "y":45.5165, 45 | "spatialReference": { 46 | "wkid": 4326 47 | } 48 | }); 49 | 50 | >> { "type": "Point", "coordinates": [ -122.6764, 45.5165 ] } 51 | ``` 52 | 53 | | Param | Type | Description | 54 | | --- | --- | --- | 55 | | JSON | object | The input ArcGIS geometry, feature or feature collection. | 56 | | [idAttribute] | string | When converting an ArcGIS Feature its attributes will contain the ID of the feature. If something other than OBJECTID or FID stores the ID, you should pass through the fieldname explicitly. | 57 | 58 | 59 | 60 | ### Terraformer.geojsonToArcGIS(GeoJSON, [idAttribute]) ⇒ object 61 | Converts [GeoJSON](https://tools.ietf.org/html/rfc7946) into ArcGIS JSON. 62 | 63 | **Kind**: static method of [Terraformer](#module_Terraformer) 64 | **Returns**: object - ArcGIS JSON. 65 | ```js 66 | import { geojsonToArcGIS } from "@terraformer/arcgis" 67 | 68 | geojsonToArcGIS({ 69 | "type": "Point", 70 | "coordinates": [45.5165, -122.6764] 71 | }) 72 | 73 | >> { "x":-122.6764, "y":45.5165, "spatialReference": { "wkid": 4326 } } 74 | ``` 75 | 76 | | Param | Type | Description | 77 | | --- | --- | --- | 78 | | GeoJSON | object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 79 | | [idAttribute] | string | When converting GeoJSON features, the id will be set as the OBJECTID unless another fieldname is supplied. | 80 | 81 | * * * 82 | 83 | ## Usage 84 | 85 | ### Browser (from CDN) 86 | 87 | This package is distributed as a [UMD](https://github.com/umdjs/umd) module and can also be used in AMD based systems or as a global under the `Terraformer` namespace. 88 | 89 | ```html 90 | 91 | ``` 92 | ```js 93 | Terraformer.arcgisToGeoJSON(/* ... */); 94 | ``` 95 | 96 | ### Node.js 97 | 98 | ```js 99 | const Terraformer = require('@terraformer/arcgis'); 100 | 101 | Terraformer.geojsonToArcGIS(/* ... */); 102 | ``` 103 | 104 | ### ES module in the browser 105 | 106 | ```html 107 | 113 | ``` 114 | 115 | ## [Contributing](./CONTRIBUTING.md) 116 | 117 | ## TypeScript 118 | 119 | Type definitions for `@terraformer/arcgis` can be found at [@types/terraformer__arcgis](https://www.npmjs.com/package/@types/terraformer__arcgis). To install into your own project: 120 | 121 | ``` 122 | npm install @types/terraformer__arcgis 123 | ``` 124 | 125 | ## Ports 126 | 127 | | Project | Language | Status | Maintainer | 128 | | - | - | - | - | 129 | | [`arcgis2geojson`](https://github.com/chris48s/arcgis2geojson/) | Python | Incomplete | [@chris48s](https://github.com/chris48s) | 130 | 131 | ## [LICENSE](https://raw.githubusercontent.com/terraformer-js/terraformer/master/LICENSE) 132 | -------------------------------------------------------------------------------- /packages/arcgis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terraformer/arcgis", 3 | "description": "Convert ArcGIS JSON geometries to GeoJSON geometries and vica-versa.", 4 | "version": "2.1.2", 5 | "author": "Patrick Arlt ", 6 | "bugs": { 7 | "url": "https://github.com/terraformer-js/terraformer/issues" 8 | }, 9 | "contributors": [ 10 | "John Gravois " 11 | ], 12 | "dependencies": { 13 | "@terraformer/common": "^2.1.2" 14 | }, 15 | "files": [ 16 | "dist/**" 17 | ], 18 | "homepage": "https://github.com/terraformer-js/terraformer", 19 | "keywords": [ 20 | "arcgis", 21 | "convert", 22 | "geo", 23 | "geojson", 24 | "geometry" 25 | ], 26 | "license": "MIT", 27 | "main": "dist/t-arcgis.umd.js", 28 | "unpkg": "dist/t-arcgis.umd.js", 29 | "jsdelivr": "dist/t-arcgis.umd.js", 30 | "module": "dist/t-arcgis.esm.js", 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/terraformer-js/terraformer" 34 | }, 35 | "scripts": { 36 | "build": "rollup -c ../../rollup.umd.config.js && rollup -c ../../rollup.esm.config.js", 37 | "doc": "jsdoc2md --files src/index.js --template README.hbs > README.md" 38 | }, 39 | "publishConfig": { 40 | "access": "public" 41 | }, 42 | "gitHead": "8fb60ae0c5a13fb7f31fc29d028d889b22091bfa" 43 | } 44 | -------------------------------------------------------------------------------- /packages/arcgis/src/arcgis.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | import { 5 | arrayIntersectsArray, 6 | coordinatesContainPoint 7 | } from '@terraformer/common'; 8 | 9 | import { 10 | closeRing, 11 | ringIsClockwise, 12 | shallowClone 13 | } from './helpers'; 14 | 15 | const coordinatesContainCoordinates = (outer, inner) => { 16 | var intersects = arrayIntersectsArray(outer, inner); 17 | var contains = coordinatesContainPoint(outer, inner[0]); 18 | if (!intersects && contains) { 19 | return true; 20 | } 21 | return false; 22 | }; 23 | 24 | // do any polygons in this array contain any other polygons in this array? 25 | // used for checking for holes in arcgis rings 26 | const convertRingsToGeoJSON = (rings) => { 27 | var outerRings = []; 28 | var holes = []; 29 | var x; // iterator 30 | var outerRing; // current outer ring being evaluated 31 | var hole; // current hole being evaluated 32 | 33 | // for each ring 34 | for (var r = 0; r < rings.length; r++) { 35 | var ring = closeRing(rings[r].slice(0)); 36 | if (ring.length < 4) { 37 | continue; 38 | } 39 | // is this ring an outer ring? is it clockwise? 40 | if (ringIsClockwise(ring)) { 41 | var polygon = [ring.slice().reverse()]; // wind outer rings counterclockwise for RFC 7946 compliance 42 | outerRings.push(polygon); // push to outer rings 43 | } else { 44 | holes.push(ring.slice().reverse()); // wind inner rings clockwise for RFC 7946 compliance 45 | } 46 | } 47 | 48 | var uncontainedHoles = []; 49 | 50 | // while there are holes left... 51 | while (holes.length) { 52 | // pop a hole off out stack 53 | hole = holes.pop(); 54 | 55 | // loop over all outer rings and see if they contain our hole. 56 | var contained = false; 57 | for (x = outerRings.length - 1; x >= 0; x--) { 58 | outerRing = outerRings[x][0]; 59 | if (coordinatesContainCoordinates(outerRing, hole)) { 60 | // the hole is contained push it into our polygon 61 | outerRings[x].push(hole); 62 | contained = true; 63 | break; 64 | } 65 | } 66 | 67 | // ring is not contained in any outer ring 68 | // sometimes this happens https://github.com/Esri/esri-leaflet/issues/320 69 | if (!contained) { 70 | uncontainedHoles.push(hole); 71 | } 72 | } 73 | 74 | // if we couldn't match any holes using contains we can try intersects... 75 | while (uncontainedHoles.length) { 76 | // pop a hole off out stack 77 | hole = uncontainedHoles.pop(); 78 | 79 | // loop over all outer rings and see if any intersect our hole. 80 | var intersects = false; 81 | 82 | for (x = outerRings.length - 1; x >= 0; x--) { 83 | outerRing = outerRings[x][0]; 84 | if (arrayIntersectsArray(outerRing, hole)) { 85 | // the hole is contained push it into our polygon 86 | outerRings[x].push(hole); 87 | intersects = true; 88 | break; 89 | } 90 | } 91 | 92 | if (!intersects) { 93 | outerRings.push([hole.reverse()]); 94 | } 95 | } 96 | 97 | if (outerRings.length === 1) { 98 | return { 99 | type: 'Polygon', 100 | coordinates: outerRings[0] 101 | }; 102 | } else { 103 | return { 104 | type: 'MultiPolygon', 105 | coordinates: outerRings 106 | }; 107 | } 108 | }; 109 | 110 | const getId = (attributes, idAttribute) => { 111 | var keys = idAttribute ? [idAttribute, 'OBJECTID', 'FID'] : ['OBJECTID', 'FID']; 112 | for (var i = 0; i < keys.length; i++) { 113 | var key = keys[i]; 114 | if ( 115 | key in attributes && 116 | (typeof attributes[key] === 'string' || 117 | typeof attributes[key] === 'number') 118 | ) { 119 | return attributes[key]; 120 | } 121 | } 122 | throw Error('No valid id attribute found'); 123 | }; 124 | 125 | export const arcgisToGeoJSON = (arcgis, idAttribute) => { 126 | var geojson = {}; 127 | 128 | if (arcgis.features) { 129 | geojson.type = 'FeatureCollection'; 130 | geojson.features = []; 131 | for (var i = 0; i < arcgis.features.length; i++) { 132 | geojson.features.push(arcgisToGeoJSON(arcgis.features[i], idAttribute)); 133 | } 134 | } 135 | 136 | if (typeof arcgis.x === 'number' && typeof arcgis.y === 'number') { 137 | geojson.type = 'Point'; 138 | geojson.coordinates = [arcgis.x, arcgis.y]; 139 | if (typeof arcgis.z === 'number') { 140 | geojson.coordinates.push(arcgis.z); 141 | } 142 | } 143 | 144 | if (arcgis.points) { 145 | geojson.type = 'MultiPoint'; 146 | geojson.coordinates = arcgis.points.slice(0); 147 | } 148 | 149 | if (arcgis.paths) { 150 | if (arcgis.paths.length === 1) { 151 | geojson.type = 'LineString'; 152 | geojson.coordinates = arcgis.paths[0].slice(0); 153 | } else { 154 | geojson.type = 'MultiLineString'; 155 | geojson.coordinates = arcgis.paths.slice(0); 156 | } 157 | } 158 | 159 | if (arcgis.rings) { 160 | geojson = convertRingsToGeoJSON(arcgis.rings.slice(0)); 161 | } 162 | 163 | if ( 164 | typeof arcgis.xmin === 'number' && 165 | typeof arcgis.ymin === 'number' && 166 | typeof arcgis.xmax === 'number' && 167 | typeof arcgis.ymax === 'number' 168 | ) { 169 | geojson.type = 'Polygon'; 170 | geojson.coordinates = [[ 171 | [arcgis.xmax, arcgis.ymax], 172 | [arcgis.xmin, arcgis.ymax], 173 | [arcgis.xmin, arcgis.ymin], 174 | [arcgis.xmax, arcgis.ymin], 175 | [arcgis.xmax, arcgis.ymax] 176 | ]]; 177 | } 178 | 179 | if (arcgis.geometry || arcgis.attributes) { 180 | geojson.type = 'Feature'; 181 | geojson.geometry = (arcgis.geometry) ? arcgisToGeoJSON(arcgis.geometry) : null; 182 | geojson.properties = (arcgis.attributes) ? shallowClone(arcgis.attributes) : null; 183 | if (arcgis.attributes) { 184 | try { 185 | geojson.id = getId(arcgis.attributes, idAttribute); 186 | } catch (err) { 187 | // don't set an id 188 | } 189 | } 190 | } 191 | 192 | // if no valid geometry was encountered 193 | if (JSON.stringify(geojson.geometry) === JSON.stringify({})) { 194 | geojson.geometry = null; 195 | } 196 | 197 | if ( 198 | arcgis.spatialReference && 199 | arcgis.spatialReference.wkid && 200 | arcgis.spatialReference.wkid !== 4326 201 | ) { 202 | console.warn('Object converted in non-standard crs - ' + JSON.stringify(arcgis.spatialReference)); 203 | } 204 | 205 | return geojson; 206 | }; 207 | -------------------------------------------------------------------------------- /packages/arcgis/src/geojson.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | import { 5 | closeRing, 6 | ringIsClockwise, 7 | shallowClone 8 | } from './helpers'; 9 | 10 | // This function ensures that rings are oriented in the right directions 11 | // outer rings are clockwise, holes are counterclockwise 12 | // used for converting GeoJSON Polygons to ArcGIS Polygons 13 | const orientRings = (poly) => { 14 | var output = []; 15 | var polygon = poly.slice(0); 16 | var outerRing = closeRing(polygon.shift().slice(0)); 17 | if (outerRing.length >= 4) { 18 | if (!ringIsClockwise(outerRing)) { 19 | outerRing.reverse(); 20 | } 21 | 22 | output.push(outerRing); 23 | 24 | for (var i = 0; i < polygon.length; i++) { 25 | var hole = closeRing(polygon[i].slice(0)); 26 | if (hole.length >= 4) { 27 | if (ringIsClockwise(hole)) { 28 | hole.reverse(); 29 | } 30 | output.push(hole); 31 | } 32 | } 33 | } 34 | 35 | return output; 36 | }; 37 | 38 | // This function flattens holes in multipolygons to one array of polygons 39 | // used for converting GeoJSON Polygons to ArcGIS Polygons 40 | const flattenMultiPolygonRings = (rings) => { 41 | var output = []; 42 | for (var i = 0; i < rings.length; i++) { 43 | var polygon = orientRings(rings[i]); 44 | for (var x = polygon.length - 1; x >= 0; x--) { 45 | var ring = polygon[x].slice(0); 46 | output.push(ring); 47 | } 48 | } 49 | return output; 50 | }; 51 | 52 | export const geojsonToArcGIS = (geojson, idAttribute) => { 53 | idAttribute = idAttribute || 'OBJECTID'; 54 | var spatialReference = { wkid: 4326 }; 55 | var result = {}; 56 | var i; 57 | 58 | switch (geojson.type) { 59 | case 'Point': 60 | result.x = geojson.coordinates[0]; 61 | result.y = geojson.coordinates[1]; 62 | if (geojson.coordinates[2] != null) { 63 | result.z = geojson.coordinates[2]; 64 | } 65 | result.spatialReference = spatialReference; 66 | break; 67 | case 'MultiPoint': 68 | result.points = geojson.coordinates.slice(0); 69 | if (geojson.coordinates[0][2] != null) { 70 | result.hasZ = true; 71 | } 72 | result.spatialReference = spatialReference; 73 | break; 74 | case 'LineString': 75 | result.paths = [geojson.coordinates.slice(0)]; 76 | if (geojson.coordinates[0][2] != null) { 77 | result.hasZ = true; 78 | } 79 | result.spatialReference = spatialReference; 80 | break; 81 | case 'MultiLineString': 82 | result.paths = geojson.coordinates.slice(0); 83 | if (geojson.coordinates[0][0][2] != null) { 84 | result.hasZ = true; 85 | } 86 | result.spatialReference = spatialReference; 87 | break; 88 | case 'Polygon': 89 | result.rings = orientRings(geojson.coordinates.slice(0)); 90 | if (geojson.coordinates[0][0][2] != null) { 91 | result.hasZ = true; 92 | } 93 | result.spatialReference = spatialReference; 94 | break; 95 | case 'MultiPolygon': 96 | result.rings = flattenMultiPolygonRings(geojson.coordinates.slice(0)); 97 | if (geojson.coordinates[0][0][0][2] != null) { 98 | result.hasZ = true; 99 | } 100 | result.spatialReference = spatialReference; 101 | break; 102 | case 'Feature': 103 | if (geojson.geometry) { 104 | result.geometry = geojsonToArcGIS(geojson.geometry, idAttribute); 105 | } 106 | result.attributes = (geojson.properties) ? shallowClone(geojson.properties) : {}; 107 | if (geojson.id) { 108 | result.attributes[idAttribute] = geojson.id; 109 | } 110 | break; 111 | case 'FeatureCollection': 112 | result = []; 113 | for (i = 0; i < geojson.features.length; i++) { 114 | result.push(geojsonToArcGIS(geojson.features[i], idAttribute)); 115 | } 116 | break; 117 | case 'GeometryCollection': 118 | result = []; 119 | for (i = 0; i < geojson.geometries.length; i++) { 120 | result.push(geojsonToArcGIS(geojson.geometries[i], idAttribute)); 121 | } 122 | break; 123 | } 124 | 125 | return result; 126 | }; 127 | -------------------------------------------------------------------------------- /packages/arcgis/src/helpers.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | import { pointsEqual } from '@terraformer/common'; 5 | 6 | // checks if the first and last points of a ring are equal and closes the ring 7 | export const closeRing = (coordinates) => { 8 | if (!pointsEqual(coordinates[0], coordinates[coordinates.length - 1])) { 9 | coordinates.push(coordinates[0]); 10 | } 11 | return coordinates; 12 | }; 13 | 14 | // determine if polygon ring coordinates are clockwise. clockwise signifies outer ring, counter-clockwise an inner ring 15 | // or hole. this logic was found at http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon- 16 | // points-are-in-clockwise-order 17 | export const ringIsClockwise = (ringToTest) => { 18 | var total = 0; 19 | var i = 0; 20 | var rLength = ringToTest.length; 21 | var pt1 = ringToTest[i]; 22 | var pt2; 23 | for (i; i < rLength - 1; i++) { 24 | pt2 = ringToTest[i + 1]; 25 | total += (pt2[0] - pt1[0]) * (pt2[1] + pt1[1]); 26 | pt1 = pt2; 27 | } 28 | return (total >= 0); 29 | }; 30 | 31 | // This function ensures that rings are oriented in the right directions 32 | // outer rings are clockwise, holes are counterclockwise 33 | // used for converting GeoJSON Polygons to ArcGIS Polygons 34 | export const orientRings = (poly) => { 35 | var output = []; 36 | var polygon = poly.slice(0); 37 | var outerRing = closeRing(polygon.shift().slice(0)); 38 | if (outerRing.length >= 4) { 39 | if (!ringIsClockwise(outerRing)) { 40 | outerRing.reverse(); 41 | } 42 | 43 | output.push(outerRing); 44 | 45 | for (var i = 0; i < polygon.length; i++) { 46 | var hole = closeRing(polygon[i].slice(0)); 47 | if (hole.length >= 4) { 48 | if (ringIsClockwise(hole)) { 49 | hole.reverse(); 50 | } 51 | output.push(hole); 52 | } 53 | } 54 | } 55 | 56 | return output; 57 | }; 58 | 59 | // This function flattens holes in multipolygons to one array of polygons 60 | // used for converting GeoJSON Polygons to ArcGIS Polygons 61 | export const flattenMultiPolygonRings = (rings) => { 62 | var output = []; 63 | for (var i = 0; i < rings.length; i++) { 64 | var polygon = orientRings(rings[i]); 65 | for (var x = polygon.length - 1; x >= 0; x--) { 66 | var ring = polygon[x].slice(0); 67 | output.push(ring); 68 | } 69 | } 70 | return output; 71 | }; 72 | 73 | // shallow object clone for feature properties and attributes 74 | // from http://jsperf.com/cloning-an-object/2 75 | export const shallowClone = (obj) => { 76 | var target = {}; 77 | for (var i in obj) { 78 | // both arcgis attributes and geojson props are just hardcoded keys 79 | if (obj.hasOwnProperty(i)) { // eslint-disable-line no-prototype-builtins 80 | target[i] = obj[i]; 81 | } 82 | } 83 | return target; 84 | }; 85 | -------------------------------------------------------------------------------- /packages/arcgis/src/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | /** @module Terraformer */ 5 | 6 | // we intentionally dont export the helper functions 7 | 8 | export { 9 | /** 10 | * Converts [ArcGIS JSON](https://developers.arcgis.com/documentation/core-concepts/features-and-geometries/) into GeoJSON. 11 | * @function 12 | * @param {object} JSON - The input ArcGIS geometry, feature or feature collection. 13 | * @param {string} [idAttribute] - When converting an ArcGIS Feature its attributes will contain the ID of the feature. If something other than OBJECTID or FID stores the ID, you should pass through the fieldname explicitly. 14 | * @return {object} GeoJSON. 15 | * ```js 16 | * import { arcgisToGeoJSON } from "@terraformer/arcgis" 17 | * 18 | * arcgisToGeoJSON({ 19 | * "x":-122.6764, 20 | * "y":45.5165, 21 | * "spatialReference": { 22 | * "wkid": 4326 23 | * } 24 | * }); 25 | * 26 | * >> { "type": "Point", "coordinates": [ -122.6764, 45.5165 ] } 27 | * ``` 28 | */ 29 | arcgisToGeoJSON 30 | } from './arcgis'; 31 | 32 | export { 33 | /** 34 | * Converts [GeoJSON](https://tools.ietf.org/html/rfc7946) into ArcGIS JSON. 35 | * @function 36 | * @param {object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 37 | * @param {string} [idAttribute] - When converting GeoJSON features, the id will be set as the OBJECTID unless another fieldname is supplied. 38 | * @return {object} ArcGIS JSON. 39 | * ```js 40 | * import { geojsonToArcGIS } from "@terraformer/arcgis" 41 | * 42 | * geojsonToArcGIS({ 43 | * "type": "Point", 44 | * "coordinates": [45.5165, -122.6764] 45 | * }) 46 | * 47 | * >> { "x":-122.6764, "y":45.5165, "spatialReference": { "wkid": 4326 } } 48 | * ``` 49 | */ 50 | geojsonToArcGIS 51 | } from './geojson'; 52 | -------------------------------------------------------------------------------- /packages/arcgis/test/arcgis.test.js: -------------------------------------------------------------------------------- 1 | 2 | import test from 'tape'; 3 | import { arcgisToGeoJSON } from '../src/index.js'; 4 | 5 | test('should exist', function (t) { 6 | t.plan(1); 7 | t.ok(arcgisToGeoJSON); 8 | }); 9 | 10 | test('should parse an ArcGIS Point in a GeoJSON Point', function (t) { 11 | t.plan(1); 12 | 13 | const input = { 14 | x: -66.796875, 15 | y: 20.0390625, 16 | spatialReference: { 17 | wkid: 4326 18 | } 19 | }; 20 | 21 | var output = arcgisToGeoJSON(input); 22 | 23 | t.deepEqual(output.coordinates, [-66.796875, 20.0390625]); 24 | }); 25 | 26 | test('should parse an ArcGIS Point in a GeoJSON Point and include z values', function (t) { 27 | t.plan(1); 28 | 29 | const input = { 30 | x: -66.796875, 31 | y: 20.0390625, 32 | z: 1, 33 | spatialReference: { 34 | wkid: 4326 35 | } 36 | }; 37 | 38 | var output = arcgisToGeoJSON(input); 39 | 40 | t.deepEqual(output.coordinates, [-66.796875, 20.0390625, 1]); 41 | }); 42 | 43 | test('should parse an ArcGIS Null Island in a GeoJSON Point', function (t) { 44 | t.plan(1); 45 | 46 | const input = { 47 | x: 0, 48 | y: 0, 49 | spatialReference: { 50 | wkid: 4326 51 | } 52 | }; 53 | 54 | var output = arcgisToGeoJSON(input); 55 | 56 | t.deepEqual(output.coordinates, [0, 0]); 57 | }); 58 | 59 | test('should not pass along geometry when nothing valid is encountered in input', function (t) { 60 | t.plan(2); 61 | 62 | const input = { 63 | geometry: { 64 | x: 'NaN', 65 | y: 'NaN' 66 | }, 67 | attributes: { 68 | foo: 'bar' 69 | } 70 | }; 71 | 72 | var output = arcgisToGeoJSON(input); 73 | 74 | t.deepEqual(output.properties.foo, 'bar'); 75 | t.deepEqual(output.geometry, null); 76 | }); 77 | 78 | test('should parse an ArcGIS Polyline in a GeoJSON LineString', function (t) { 79 | t.plan(1); 80 | 81 | const input = { 82 | paths: [ 83 | [[6.6796875, 47.8125], 84 | [-65.390625, 52.3828125], 85 | [-52.3828125, 42.5390625]] 86 | ], 87 | spatialReference: { 88 | wkid: 4326 89 | } 90 | }; 91 | 92 | var output = arcgisToGeoJSON(input); 93 | 94 | t.deepEqual(output.coordinates, [ 95 | [6.6796875, 47.8125], 96 | [-65.390625, 52.3828125], 97 | [-52.3828125, 42.5390625] 98 | ]); 99 | }); 100 | 101 | test('should parse an ArcGIS Polyline in a GeoJSON LineString and include z values', function (t) { 102 | t.plan(1); 103 | 104 | const input = { 105 | paths: [ 106 | [[6.6796875, 47.8125, 1], 107 | [-65.390625, 52.3828125, 1], 108 | [-52.3828125, 42.5390625, 1]] 109 | ], 110 | spatialReference: { 111 | wkid: 4326 112 | } 113 | }; 114 | 115 | var output = arcgisToGeoJSON(input); 116 | 117 | t.deepEqual(output.coordinates, [ 118 | [6.6796875, 47.8125, 1], 119 | [-65.390625, 52.3828125, 1], 120 | [-52.3828125, 42.5390625, 1] 121 | ]); 122 | }); 123 | 124 | test('should parse an ArcGIS Polygon in a GeoJSON Polygon', function (t) { 125 | t.plan(2); 126 | 127 | const input = { 128 | rings: [ 129 | [ 130 | [41.8359375, 71.015625], 131 | [56.953125, 33.75], 132 | [21.796875, 36.5625], 133 | [41.8359375, 71.015625] 134 | ] 135 | ], 136 | spatialReference: { 137 | wkid: 4326 138 | } 139 | }; 140 | 141 | var output = arcgisToGeoJSON(input); 142 | 143 | t.deepEqual(output.coordinates, [ 144 | [ 145 | [41.8359375, 71.015625], 146 | [21.796875, 36.5625], 147 | [56.953125, 33.75], 148 | [41.8359375, 71.015625] 149 | ] 150 | ]); 151 | 152 | t.equal(output.type, 'Polygon'); 153 | }); 154 | 155 | test('should parse an ArcGIS Polygon in a GeoJSON Polygon and include z values', function (t) { 156 | t.plan(2); 157 | 158 | const input = { 159 | rings: [ 160 | [ 161 | [41.8359375, 71.015625, 1], 162 | [56.953125, 33.75, 1], 163 | [21.796875, 36.5625, 1], 164 | [41.8359375, 71.015625, 1] 165 | ] 166 | ], 167 | spatialReference: { 168 | wkid: 4326 169 | } 170 | }; 171 | 172 | var output = arcgisToGeoJSON(input); 173 | 174 | t.deepEqual(output.coordinates, [ 175 | [ 176 | [41.8359375, 71.015625, 1], 177 | [21.796875, 36.5625, 1], 178 | [56.953125, 33.75, 1], 179 | [41.8359375, 71.015625, 1] 180 | ] 181 | ]); 182 | 183 | t.equal(output.type, 'Polygon'); 184 | }); 185 | 186 | test('should close rings when parsing an ArcGIS Polygon in a GeoJSON Polygon', function (t) { 187 | t.plan(2); 188 | 189 | const input = { 190 | rings: [ 191 | [ 192 | [41.8359375, 71.015625], 193 | [56.953125, 33.75], 194 | [21.796875, 36.5625] 195 | ] 196 | ], 197 | spatialReference: { 198 | wkid: 4326 199 | } 200 | }; 201 | 202 | var output = arcgisToGeoJSON(input); 203 | 204 | t.deepEqual(output.coordinates, [ 205 | [ 206 | [41.8359375, 71.015625], 207 | [21.796875, 36.5625], 208 | [56.953125, 33.75], 209 | [41.8359375, 71.015625] 210 | ] 211 | ]); 212 | 213 | t.equal(output.type, 'Polygon'); 214 | }); 215 | 216 | test('should parse an ArcGIS Multipoint in a GeoJSON MultiPoint', function (t) { 217 | t.plan(1); 218 | 219 | const input = { 220 | points: [ 221 | [41.8359375, 71.015625], 222 | [56.953125, 33.75], 223 | [21.796875, 36.5625] 224 | ], 225 | spatialReference: { 226 | wkid: 4326 227 | } 228 | }; 229 | 230 | var output = arcgisToGeoJSON(input); 231 | 232 | t.deepEqual(output.coordinates, [ 233 | [41.8359375, 71.015625], 234 | [56.953125, 33.75], 235 | [21.796875, 36.5625] 236 | ]); 237 | }); 238 | 239 | test('should parse an ArcGIS Polyline in a GeoJSON MultiLineString', function (t) { 240 | t.plan(1); 241 | 242 | const input = { 243 | paths: [ 244 | [ 245 | [41.8359375, 71.015625], 246 | [56.953125, 33.75] 247 | ], 248 | [ 249 | [21.796875, 36.5625], 250 | [41.8359375, 71.015625] 251 | ] 252 | ], 253 | spatialReference: { 254 | wkid: 4326 255 | } 256 | }; 257 | 258 | var output = arcgisToGeoJSON(input); 259 | 260 | t.deepEqual(output.coordinates, [ 261 | [ 262 | [41.8359375, 71.015625], 263 | [56.953125, 33.75] 264 | ], 265 | [ 266 | [21.796875, 36.5625], 267 | [41.8359375, 71.015625] 268 | ] 269 | ]); 270 | }); 271 | 272 | test('should parse an ArcGIS Polygon in a GeoJSON MultiPolygon', function (t) { 273 | t.plan(2); 274 | 275 | const input = { 276 | rings: [ 277 | [ 278 | [-122.63, 45.52], 279 | [-122.57, 45.53], 280 | [-122.52, 45.50], 281 | [-122.49, 45.48], 282 | [-122.64, 45.49], 283 | [-122.63, 45.52], 284 | [-122.63, 45.52] 285 | ], 286 | [ 287 | [-83, 35], 288 | [-74, 35], 289 | [-74, 41], 290 | [-83, 41], 291 | [-83, 35] 292 | ] 293 | ], 294 | spatialReference: { 295 | wkid: 4326 296 | } 297 | }; 298 | 299 | var output = arcgisToGeoJSON(input); 300 | 301 | var expected = [ 302 | [ 303 | [ 304 | [-122.63, 45.52], 305 | [-122.63, 45.52], 306 | [-122.64, 45.49], 307 | [-122.49, 45.48], 308 | [-122.52, 45.5], 309 | [-122.57, 45.53], 310 | [-122.63, 45.52] 311 | ] 312 | ], 313 | [ 314 | [ 315 | [-83, 35], 316 | [-74, 35], 317 | [-74, 41], 318 | [-83, 41], 319 | [-83, 35] 320 | ] 321 | ] 322 | ]; 323 | 324 | t.deepEqual(output.coordinates, expected); 325 | t.equal(output.type, 'MultiPolygon'); 326 | }); 327 | 328 | test('should strip invalid rings when converting ArcGIS Polygons to GeoJSON', function (t) { 329 | t.plan(2); 330 | 331 | const input = { 332 | rings: [ 333 | [ 334 | [-122.63, 45.52], 335 | [-122.57, 45.53], 336 | [-122.52, 45.50], 337 | [-122.49, 45.48], 338 | [-122.64, 45.49], 339 | [-122.63, 45.52], 340 | [-122.63, 45.52] 341 | ], 342 | [ 343 | [-83, 35], 344 | [-74, 35], 345 | [-83, 35] 346 | ] 347 | ], 348 | spatialReference: { 349 | wkid: 4326 350 | } 351 | }; 352 | 353 | var output = arcgisToGeoJSON(input); 354 | 355 | t.deepEqual(output.coordinates, [ 356 | [ 357 | [-122.63, 45.52], 358 | [-122.63, 45.52], 359 | [-122.64, 45.49], 360 | [-122.49, 45.48], 361 | [-122.52, 45.5], 362 | [-122.57, 45.53], 363 | [-122.63, 45.52] 364 | ] 365 | ]); 366 | t.equal(output.type, 'Polygon'); 367 | }); 368 | 369 | test('should properly close rings when converting an ArcGIS Polygon in a GeoJSON MultiPolygon', function (t) { 370 | t.plan(2); 371 | 372 | const input = { 373 | rings: [ 374 | [ 375 | [-122.63, 45.52], 376 | [-122.57, 45.53], 377 | [-122.52, 45.50], 378 | [-122.49, 45.48], 379 | [-122.64, 45.49] 380 | ], 381 | [ 382 | [-83, 35], 383 | [-74, 35], 384 | [-74, 41], 385 | [-83, 41] 386 | ] 387 | ], 388 | spatialReference: { 389 | wkid: 4326 390 | } 391 | }; 392 | 393 | var output = arcgisToGeoJSON(input); 394 | 395 | t.deepEquals(output.coordinates, [ 396 | [ 397 | [ 398 | [-122.63, 45.52], 399 | [-122.64, 45.49], 400 | [-122.49, 45.48], 401 | [-122.52, 45.5], 402 | [-122.57, 45.53], 403 | [-122.63, 45.52] 404 | ] 405 | ], 406 | [ 407 | [ 408 | [-83, 35], 409 | [-74, 35], 410 | [-74, 41], 411 | [-83, 41], 412 | [-83, 35] 413 | ] 414 | ] 415 | ]); 416 | 417 | t.equal(output.type, 'MultiPolygon'); 418 | }); 419 | 420 | test('should parse an ArcGIS MultiPolygon with holes to a GeoJSON MultiPolygon', function (t) { 421 | t.plan(2); 422 | 423 | const input = { 424 | type: 'Polygon', 425 | rings: [ 426 | [ 427 | [-100.74462180954974, 39.95017165502381], 428 | [-94.50439384003792, 39.91647453608879], 429 | [-94.41650267263967, 34.89313438177965], 430 | [-100.78856739324887, 34.85708140996771], 431 | [-100.74462180954974, 39.95017165502381] 432 | ], 433 | [ 434 | [-99.68993678392353, 39.341088433448896], 435 | [-99.68993678392353, 38.24507658785885], 436 | [-98.67919734199646, 37.86444431771113], 437 | [-98.06395917020868, 38.210554846669694], 438 | [-98.06395917020868, 39.341088433448896], 439 | [-99.68993678392353, 39.341088433448896] 440 | ], 441 | [ 442 | [-96.83349180978595, 37.23732027507514], 443 | [-97.31689323047635, 35.967330282988534], 444 | [-96.5698183075912, 35.57512048069255], 445 | [-95.42724211456674, 36.357601429255965], 446 | [-96.83349180978595, 37.23732027507514] 447 | ], 448 | [ 449 | [-101.4916967324349, 38.24507658785885], 450 | [-101.44775114873578, 36.073960493943744], 451 | [-103.95263145328033, 36.03843312329154], 452 | [-103.68895795108557, 38.03770050767439], 453 | [-101.4916967324349, 38.24507658785885] 454 | ] 455 | ], 456 | spatialReference: { 457 | wkid: 4326 458 | } 459 | }; 460 | 461 | var output = arcgisToGeoJSON(input); 462 | 463 | t.deepEquals(output.coordinates, [ 464 | [ 465 | [[-100.74462180954974, 39.95017165502381], [-100.78856739324887, 34.85708140996771], [-94.41650267263967, 34.89313438177965], [-94.50439384003792, 39.91647453608879], [-100.74462180954974, 39.95017165502381]], 466 | [[-96.83349180978595, 37.23732027507514], [-95.42724211456674, 36.357601429255965], [-96.5698183075912, 35.57512048069255], [-97.31689323047635, 35.967330282988534], [-96.83349180978595, 37.23732027507514]], 467 | [[-99.68993678392353, 39.341088433448896], [-98.06395917020868, 39.341088433448896], [-98.06395917020868, 38.210554846669694], [-98.67919734199646, 37.86444431771113], [-99.68993678392353, 38.24507658785885], [-99.68993678392353, 39.341088433448896]] 468 | ], 469 | [ 470 | [[-101.4916967324349, 38.24507658785885], [-103.68895795108557, 38.03770050767439], [-103.95263145328033, 36.03843312329154], [-101.44775114873578, 36.073960493943744], [-101.4916967324349, 38.24507658785885]] 471 | ] 472 | ]); 473 | 474 | t.equal(output.type, 'MultiPolygon'); 475 | }); 476 | 477 | test('should still parse holes outside the outer rings', function (t) { 478 | t.plan(1); 479 | 480 | const input = { 481 | rings: [ 482 | [[-122.45, 45.63], [-122.45, 45.68], [-122.39, 45.68], [-122.39, 45.63], [-122.45, 45.63]], 483 | [[-122.46, 45.64], [-122.4, 45.64], [-122.4, 45.66], [-122.46, 45.66], [-122.46, 45.64]] 484 | ] 485 | }; 486 | 487 | var output = arcgisToGeoJSON(input); 488 | 489 | var expected = [ 490 | [[-122.45, 45.63], [-122.39, 45.63], [-122.39, 45.68], [-122.45, 45.68], [-122.45, 45.63]], 491 | [[-122.46, 45.64], [-122.46, 45.66], [-122.4, 45.66], [-122.4, 45.64], [-122.46, 45.64]] 492 | ]; 493 | 494 | t.deepEquals(output.coordinates, expected); 495 | }); 496 | 497 | test('should parse an ArcGIS Feature into a GeoJSON Feature', function (t) { 498 | t.plan(2); 499 | 500 | const input = { 501 | geometry: { 502 | rings: [ 503 | [[41.8359375, 71.015625], 504 | [56.953125, 33.75], 505 | [21.796875, 36.5625], 506 | [41.8359375, 71.015625]] 507 | ], 508 | spatialReference: { 509 | wkid: 4326 510 | } 511 | }, 512 | attributes: { 513 | foo: 'bar' 514 | } 515 | }; 516 | 517 | var output = arcgisToGeoJSON(input); 518 | 519 | t.deepEqual(output.geometry.coordinates, [ 520 | [[41.8359375, 71.015625], 521 | [21.796875, 36.5625], 522 | [56.953125, 33.75], 523 | [41.8359375, 71.015625]] 524 | ]); 525 | 526 | t.equal(output.geometry.type, 'Polygon'); 527 | }); 528 | 529 | test('should convert ArcGIS JSON with an array of ArcGIS Features into a GeoJSON FeatureCollection', function (t) { 530 | t.plan(1); 531 | 532 | const input = { 533 | displayFieldName: 'prop0', 534 | fieldAliases: { prop0: 'prop0' }, 535 | geometryType: 'esriGeometryPolygon', 536 | fields: [ 537 | { 538 | name: 'prop0', 539 | type: 'esriFieldTypeString', 540 | alias: 'prop0', 541 | length: 20 542 | }, 543 | { 544 | name: 'OBJECTID', 545 | type: 'esriFieldTypeOID', 546 | alias: 'OBJECTID' 547 | }, 548 | { 549 | name: 'FID', 550 | type: 'esriFieldTypeDouble', 551 | alias: 'FID' 552 | } 553 | ], 554 | spatialReference: { wkid: 4326 }, 555 | features: [ 556 | { 557 | geometry: { 558 | x: 102, 559 | y: 0.5 560 | }, 561 | attributes: { 562 | prop0: 'value0', 563 | OBJECTID: 0, 564 | FID: 0 565 | } 566 | }, { 567 | geometry: { 568 | paths: [ 569 | [[102, 0], 570 | [103, 1], 571 | [104, 0], 572 | [105, 1]] 573 | ] 574 | }, 575 | attributes: { 576 | prop0: null, 577 | OBJECTID: null, 578 | FID: 1 579 | } 580 | }, { 581 | geometry: { 582 | rings: [ 583 | [[100, 0], 584 | [100, 1], 585 | [101, 1], 586 | [101, 0], 587 | [100, 0]] 588 | ] 589 | }, 590 | attributes: { 591 | prop0: null, 592 | OBJECTID: 2, 593 | FID: 30.25 594 | } 595 | } 596 | ] 597 | }; 598 | 599 | var output = arcgisToGeoJSON(input, 'prop0'); 600 | 601 | t.deepEqual(output, { 602 | type: 'FeatureCollection', 603 | features: [{ 604 | type: 'Feature', 605 | geometry: { 606 | type: 'Point', 607 | coordinates: [102.0, 0.5] 608 | }, 609 | properties: { 610 | prop0: 'value0', 611 | OBJECTID: 0, 612 | FID: 0 613 | }, 614 | id: 'value0' 615 | }, { 616 | type: 'Feature', 617 | geometry: { 618 | type: 'LineString', 619 | coordinates: [ 620 | [102.0, 0.0], 621 | [103.0, 1.0], 622 | [104.0, 0.0], 623 | [105.0, 1.0] 624 | ] 625 | }, 626 | properties: { 627 | prop0: null, 628 | OBJECTID: null, 629 | FID: 1 630 | }, 631 | id: 1 632 | }, { 633 | type: 'Feature', 634 | geometry: { 635 | type: 'Polygon', 636 | coordinates: [ 637 | [[100.0, 0.0], 638 | [101.0, 0.0], 639 | [101.0, 1.0], 640 | [100.0, 1.0], 641 | [100.0, 0.0]] 642 | ] 643 | }, 644 | properties: { 645 | prop0: null, 646 | OBJECTID: 2, 647 | FID: 30.25 648 | }, 649 | id: 2 650 | }] 651 | }); 652 | }); 653 | 654 | test('should parse an ArcGIS Feature w/ OBJECTID into a GeoJSON Feature', function (t) { 655 | t.plan(1); 656 | 657 | const input = { 658 | geometry: { 659 | rings: [ 660 | [[41.8359375, 71.015625], 661 | [56.953125, 33.75], 662 | [21.796875, 36.5625], 663 | [41.8359375, 71.015625]] 664 | ], 665 | spatialReference: { 666 | wkid: 4326 667 | } 668 | }, 669 | attributes: { 670 | OBJECTID: 123 671 | } 672 | }; 673 | 674 | var output = arcgisToGeoJSON(input); 675 | 676 | t.equal(output.id, 123); 677 | }); 678 | 679 | test('should parse an ArcGIS Feature w/ FID into a GeoJSON Feature', function (t) { 680 | t.plan(1); 681 | 682 | const input = { 683 | geometry: { 684 | rings: [ 685 | [[41.8359375, 71.015625], 686 | [56.953125, 33.75], 687 | [21.796875, 36.5625], 688 | [41.8359375, 71.015625]] 689 | ], 690 | spatialReference: { 691 | wkid: 4326 692 | } 693 | }, 694 | attributes: { 695 | FID: 123 696 | } 697 | }; 698 | 699 | var output = arcgisToGeoJSON(input); 700 | 701 | t.equal(output.id, 123); 702 | }); 703 | 704 | test('should parse an ArcGIS Feature w/ a custom id into a GeoJSON Feature', function (t) { 705 | t.plan(1); 706 | 707 | const input = { 708 | geometry: { 709 | rings: [ 710 | [[41.8359375, 71.015625], 711 | [56.953125, 33.75], 712 | [21.796875, 36.5625], 713 | [41.8359375, 71.015625]] 714 | ], 715 | spatialReference: { 716 | wkid: 4326 717 | } 718 | }, 719 | attributes: { 720 | FooId: 123 721 | } 722 | }; 723 | 724 | var output = arcgisToGeoJSON(input, 'FooId'); 725 | 726 | t.equal(output.id, 123); 727 | }); 728 | 729 | test('should parse an ArcGIS Feature w/ empty attributes into a GeoJSON Feature', function (t) { 730 | t.plan(2); 731 | 732 | const input = { 733 | geometry: { 734 | rings: [ 735 | [[41.8359375, 71.015625], 736 | [56.953125, 33.75], 737 | [21.796875, 36.5625], 738 | [41.8359375, 71.015625]] 739 | ], 740 | spatialReference: { 741 | wkid: 4326 742 | } 743 | }, 744 | attributes: {} 745 | }; 746 | 747 | var output = arcgisToGeoJSON(input); 748 | 749 | t.deepEqual(output.geometry.coordinates, [ 750 | [ 751 | [41.8359375, 71.015625], 752 | [21.796875, 36.5625], 753 | [56.953125, 33.75], 754 | [41.8359375, 71.015625] 755 | ] 756 | ]); 757 | 758 | t.equal(output.geometry.type, 'Polygon'); 759 | }); 760 | 761 | test('should parse an ArcGIS Feature w/ no attributes into a GeoJSON Feature', function (t) { 762 | t.plan(3); 763 | 764 | const input = { 765 | geometry: { 766 | rings: [ 767 | [ 768 | [41.8359375, 71.015625], 769 | [56.953125, 33.75], 770 | [21.796875, 36.5625], 771 | [41.8359375, 71.015625] 772 | ] 773 | ], 774 | spatialReference: { 775 | wkid: 4326 776 | } 777 | } 778 | }; 779 | 780 | var output = arcgisToGeoJSON(input); 781 | 782 | t.equal(output.geometry.type, 'Polygon'); 783 | t.equal(output.properties, null); 784 | t.deepEqual(output.geometry.coordinates, [ 785 | [ 786 | [41.8359375, 71.015625], 787 | [21.796875, 36.5625], 788 | [56.953125, 33.75], 789 | [41.8359375, 71.015625] 790 | ] 791 | ]); 792 | }); 793 | 794 | test('should parse an ArcGIS Feature w/ no geometry into a GeoJSON Feature', function (t) { 795 | t.plan(2); 796 | 797 | const input = { 798 | attributes: { 799 | foo: 'bar' 800 | } 801 | }; 802 | 803 | var output = arcgisToGeoJSON(input); 804 | 805 | t.deepEqual(output.geometry, null); 806 | t.deepEqual(output.properties.foo, 'bar'); 807 | }); 808 | 809 | test('should not allow GeoJSON Feature with id field that is not string or number', function (t) { 810 | t.plan(1); 811 | 812 | const input = { 813 | geometry: { 814 | x: -66.796875, 815 | y: 20.0390625, 816 | spatialReference: { 817 | wkid: 4326 818 | } 819 | }, 820 | attributes: { 821 | OBJECTID: 123, 822 | some_field: { 823 | 'not an number': 'or a string' 824 | } 825 | } 826 | }; 827 | 828 | var output = arcgisToGeoJSON(input, 'some_field'); 829 | 830 | // 'some_field' isn't a number - fallback to OBJECTID 831 | t.equal(output.id, 123); 832 | }); 833 | 834 | test('should use a custom field value and not OBJECTID for an id when both are present', function (t) { 835 | t.plan(1); 836 | 837 | const input = { 838 | geometry: { 839 | x: -66.796875, 840 | y: 20.0390625, 841 | spatialReference: { 842 | wkid: 4326 843 | } 844 | }, 845 | attributes: { 846 | OBJECTID: 123, 847 | otherIdField: 456 848 | } 849 | }; 850 | 851 | var output = arcgisToGeoJSON(input, 'otherIdField'); 852 | 853 | // 'some_field' isn't a number - fallback to OBJECTID 854 | t.equal(output.id, 456); 855 | }); 856 | 857 | test('should not allow GeoJSON Feature with id: undefined', function (t) { 858 | t.plan(1); 859 | 860 | const input = { 861 | geometry: { 862 | x: -66.796875, 863 | y: 20.0390625, 864 | spatialReference: { 865 | wkid: 4326 866 | } 867 | }, 868 | // no 'OBJECTID' or 'FID' in 'attributes' 869 | attributes: { 870 | foo: 'bar' 871 | } 872 | }; 873 | 874 | var output = arcgisToGeoJSON(input); 875 | 876 | // output should not have an id key 877 | t.equal(true, !('id' in output)); 878 | }); 879 | 880 | test('should log warning when converting SRID other than 4326 without CRS attribute', function (t) { 881 | t.plan(3); 882 | 883 | const input = { 884 | x: 392917.31, 885 | y: 298521.34, 886 | spatialReference: { 887 | wkid: 27700 888 | } 889 | }; 890 | 891 | // mock out console.warn so we can test logging a warning 892 | console.warn = function (text) { 893 | t.equal( 894 | true, 895 | (text.indexOf('Object converted in non-standard crs') !== -1) 896 | ); 897 | }; 898 | var output = arcgisToGeoJSON(input); 899 | 900 | // output should not have a crs key 901 | t.equal(true, !('crs' in output)); 902 | t.deepEqual(output.coordinates, [392917.31, 298521.34]); 903 | }); 904 | 905 | test('should not modify the original ArcGIS Geometry', function (t) { 906 | t.plan(1); 907 | 908 | const input = { 909 | geometry: { 910 | rings: [ 911 | [ 912 | [41.8359375, 71.015625], 913 | [56.953125, 33.75], 914 | [21.796875, 36.5625], 915 | [41.8359375, 71.015625] 916 | ] 917 | ], 918 | spatialReference: { 919 | wkid: 4326 920 | } 921 | }, 922 | attributes: { 923 | foo: 'bar' 924 | } 925 | }; 926 | 927 | var original = JSON.stringify(input); 928 | 929 | arcgisToGeoJSON(input); 930 | 931 | t.equal(original, JSON.stringify(input)); 932 | }); 933 | 934 | test('should parse an ArcGIS Extent into a Terraformer GeoJSON Polygon', function (t) { 935 | t.plan(2); 936 | 937 | const input = { 938 | xmax: -35.5078125, 939 | ymax: 41.244772343082076, 940 | xmin: -13.7109375, 941 | ymin: 54.36775852406841, 942 | spatialReference: { 943 | wkid: 4326 944 | } 945 | }; 946 | 947 | var output = arcgisToGeoJSON(input); 948 | 949 | t.deepEqual(output.coordinates, [[[-35.5078125, 41.244772343082076], [-13.7109375, 41.244772343082076], [-13.7109375, 54.36775852406841], [-35.5078125, 54.36775852406841], [-35.5078125, 41.244772343082076]]]); 950 | t.equal(output.type, 'Polygon'); 951 | }); 952 | -------------------------------------------------------------------------------- /packages/arcgis/test/geojson.test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | import { geojsonToArcGIS } from '../src/index.js'; 3 | 4 | test('should exist', function (t) { 5 | t.plan(1); 6 | t.ok(geojsonToArcGIS); 7 | }); 8 | 9 | test('should convert a GeoJSON Point to an ArcGIS Point', function (t) { 10 | t.plan(1); 11 | 12 | const input = { 13 | type: 'Point', 14 | coordinates: [-58.7109375, 47.4609375] 15 | }; 16 | 17 | var output = geojsonToArcGIS(input); 18 | 19 | t.deepEqual(output, { 20 | x: -58.7109375, 21 | y: 47.4609375, 22 | spatialReference: { 23 | wkid: 4326 24 | } 25 | }); 26 | }); 27 | 28 | test('should convert a GeoJSON Point to an ArcGIS Point and include z values', function (t) { 29 | t.plan(1); 30 | 31 | const input = { 32 | type: 'Point', 33 | coordinates: [-58.7109375, 47.4609375, 10] 34 | }; 35 | 36 | var output = geojsonToArcGIS(input); 37 | 38 | t.deepEqual(output, { 39 | x: -58.7109375, 40 | y: 47.4609375, 41 | z: 10, 42 | spatialReference: { 43 | wkid: 4326 44 | } 45 | }); 46 | }); 47 | 48 | test('should convert a GeoJSON Point to an ArcGIS Point and include a z value of 0', function (t) { 49 | t.plan(1); 50 | 51 | const input = { 52 | type: 'Point', 53 | coordinates: [-58.7109375, 47.4609375, 0] 54 | }; 55 | 56 | var output = geojsonToArcGIS(input); 57 | 58 | t.deepEqual(output, { 59 | x: -58.7109375, 60 | y: 47.4609375, 61 | z: 0, 62 | spatialReference: { 63 | wkid: 4326 64 | } 65 | }); 66 | }); 67 | 68 | test('should convert a GeoJSON Null Island to an ArcGIS Point', function (t) { 69 | t.plan(1); 70 | 71 | const input = { 72 | type: 'Point', 73 | coordinates: [0, 0] 74 | }; 75 | 76 | var output = geojsonToArcGIS(input); 77 | 78 | t.deepEqual(output, { 79 | x: 0, 80 | y: 0, 81 | spatialReference: { 82 | wkid: 4326 83 | } 84 | }); 85 | }); 86 | 87 | test('should convert a GeoJSON LineString to an ArcGIS Polyline', function (t) { 88 | t.plan(1); 89 | 90 | const input = { 91 | type: 'LineString', 92 | coordinates: [ 93 | [21.4453125, -14.0625], 94 | [33.3984375, -20.7421875], 95 | [38.3203125, -24.609375] 96 | ] 97 | }; 98 | 99 | var output = geojsonToArcGIS(input); 100 | 101 | t.deepEqual(output, { 102 | paths: [ 103 | [ 104 | [21.4453125, -14.0625], 105 | [33.3984375, -20.7421875], 106 | [38.3203125, -24.609375] 107 | ] 108 | ], 109 | spatialReference: { 110 | wkid: 4326 111 | } 112 | }); 113 | }); 114 | 115 | test('should convert a GeoJSON LineString to an ArcGIS Polyline and include z values', function (t) { 116 | t.plan(1); 117 | 118 | const input = { 119 | type: 'LineString', 120 | coordinates: [ 121 | [21.4453125, -14.0625, 10], 122 | [33.3984375, -20.7421875, 15], 123 | [38.3203125, -24.609375, 12] 124 | ] 125 | }; 126 | 127 | var output = geojsonToArcGIS(input); 128 | 129 | t.deepEqual(output, { 130 | paths: [ 131 | [ 132 | [21.4453125, -14.0625, 10], 133 | [33.3984375, -20.7421875, 15], 134 | [38.3203125, -24.609375, 12] 135 | ] 136 | ], 137 | hasZ: true, 138 | spatialReference: { 139 | wkid: 4326 140 | } 141 | }); 142 | }); 143 | 144 | test('should convert a GeoJSON Polygon to an ArcGIS Polygon', function (t) { 145 | t.plan(1); 146 | 147 | const input = { 148 | type: 'Polygon', 149 | coordinates: [ 150 | [ 151 | [41.8359375, 71.015625], 152 | [56.953125, 33.75], 153 | [21.796875, 36.5625], 154 | [41.8359375, 71.015625] 155 | ] 156 | ] 157 | }; 158 | 159 | var output = geojsonToArcGIS(input); 160 | 161 | t.deepEqual(output, { 162 | rings: [ 163 | [ 164 | [41.8359375, 71.015625], 165 | [56.953125, 33.75], 166 | [21.796875, 36.5625], 167 | [41.8359375, 71.015625] 168 | ] 169 | ], 170 | spatialReference: { 171 | wkid: 4326 172 | } 173 | }); 174 | }); 175 | 176 | test('should convert a GeoJSON Polygon to an ArcGIS Polygon and include z values', function (t) { 177 | t.plan(1); 178 | 179 | const input = { 180 | type: 'Polygon', 181 | coordinates: [ 182 | [ 183 | [41.8359375, 71.015625, 10], 184 | [56.953125, 33.75, 15], 185 | [21.796875, 36.5625, 12], 186 | [41.8359375, 71.015625, 10] 187 | ] 188 | ] 189 | }; 190 | 191 | var output = geojsonToArcGIS(input); 192 | 193 | t.deepEqual(output, { 194 | rings: [ 195 | [ 196 | [41.8359375, 71.015625, 10], 197 | [56.953125, 33.75, 15], 198 | [21.796875, 36.5625, 12], 199 | [41.8359375, 71.015625, 10] 200 | ] 201 | ], 202 | hasZ: true, 203 | spatialReference: { 204 | wkid: 4326 205 | } 206 | }); 207 | }); 208 | 209 | test('should convert a GeoJSON Polygon w/ a hole to an ArcGIS Polygon w/ 2 rings', function (t) { 210 | t.plan(1); 211 | 212 | const input = { 213 | type: 'Polygon', 214 | coordinates: [ 215 | [ 216 | [100.0, 0.0], 217 | [101.0, 0.0], 218 | [101.0, 1.0], 219 | [100.0, 1.0], 220 | [100.0, 0.0] 221 | ], 222 | [ 223 | [100.2, 0.2], 224 | [100.8, 0.2], 225 | [100.8, 0.8], 226 | [100.2, 0.8], 227 | [100.2, 0.2] 228 | ] 229 | ] 230 | }; 231 | 232 | var output = geojsonToArcGIS(input); 233 | 234 | t.deepEqual(output, { 235 | rings: [ 236 | [[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]], 237 | [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]] 238 | ], 239 | spatialReference: { 240 | wkid: 4326 241 | } 242 | }); 243 | }); 244 | 245 | test('should strip invalid rings when converting a GeoJSON Polygon to and ArcGIS Polygon', function (t) { 246 | t.plan(1); 247 | 248 | const input = { 249 | type: 'Polygon', 250 | coordinates: [ 251 | [ 252 | [100.0, 0.0], 253 | [101.0, 0.0], 254 | [101.0, 1.0], 255 | [100.0, 1.0], 256 | [100.0, 0.0] 257 | ], 258 | [ 259 | [100.2, 0.2], 260 | [100.8, 0.2], 261 | [100.2, 0.2] 262 | ] 263 | ] 264 | }; 265 | 266 | var output = geojsonToArcGIS(input); 267 | 268 | t.deepEqual(output, { 269 | rings: [ 270 | [[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]] 271 | ], 272 | spatialReference: { 273 | wkid: 4326 274 | } 275 | }); 276 | }); 277 | 278 | test('should close ring when converting a GeoJSON Polygon w/ a hole to an ArcGIS Polygon', function (t) { 279 | t.plan(1); 280 | 281 | const input = { 282 | type: 'Polygon', 283 | coordinates: [ 284 | [ 285 | [100.0, 0.0], 286 | [101.0, 0.0], 287 | [101.0, 1.0], 288 | [100.0, 1.0] 289 | ], 290 | [ 291 | [100.2, 0.2], 292 | [100.8, 0.2], 293 | [100.8, 0.8], 294 | [100.2, 0.8] 295 | ] 296 | ] 297 | }; 298 | 299 | var output = geojsonToArcGIS(input); 300 | 301 | t.deepEqual(output, { 302 | rings: [ 303 | [[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]], 304 | [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]] 305 | ], 306 | spatialReference: { 307 | wkid: 4326 308 | } 309 | }); 310 | }); 311 | 312 | test('should convert a GeoJSON MultiPoint to an ArcGIS Multipoint', function (t) { 313 | t.plan(1); 314 | 315 | const input = { 316 | type: 'MultiPoint', 317 | coordinates: [ 318 | [41.8359375, 71.015625], 319 | [56.953125, 33.75], 320 | [21.796875, 36.5625] 321 | ] 322 | }; 323 | 324 | var output = geojsonToArcGIS(input); 325 | 326 | t.deepEqual(output, { 327 | points: [ 328 | [41.8359375, 71.015625], 329 | [56.953125, 33.75], 330 | [21.796875, 36.5625] 331 | ], 332 | spatialReference: { 333 | wkid: 4326 334 | } 335 | }); 336 | }); 337 | 338 | test('should convert a GeoJSON MultiPoint to an ArcGIS Multipoint and include z values', function (t) { 339 | t.plan(1); 340 | 341 | const input = { 342 | type: 'MultiPoint', 343 | coordinates: [ 344 | [41.8359375, 71.015625, 10], 345 | [56.953125, 33.75, 15], 346 | [21.796875, 36.5625, 12] 347 | ] 348 | }; 349 | 350 | var output = geojsonToArcGIS(input); 351 | 352 | t.deepEqual(output, { 353 | points: [ 354 | [41.8359375, 71.015625, 10], 355 | [56.953125, 33.75, 15], 356 | [21.796875, 36.5625, 12] 357 | ], 358 | hasZ: true, 359 | spatialReference: { 360 | wkid: 4326 361 | } 362 | }); 363 | }); 364 | 365 | test('should convert a GeoJSON MultiLineString to an ArcGIS Polyline', function (t) { 366 | t.plan(1); 367 | 368 | const input = { 369 | type: 'MultiLineString', 370 | coordinates: [ 371 | [ 372 | [41.8359375, 71.015625], 373 | [56.953125, 33.75] 374 | ], 375 | [ 376 | [21.796875, 36.5625], 377 | [47.8359375, 71.015625] 378 | ] 379 | ] 380 | }; 381 | 382 | var output = geojsonToArcGIS(input); 383 | 384 | t.deepEqual(output, { 385 | paths: [ 386 | [ 387 | [41.8359375, 71.015625], 388 | [56.953125, 33.75] 389 | ], 390 | [ 391 | [21.796875, 36.5625], 392 | [47.8359375, 71.015625] 393 | ] 394 | ], 395 | spatialReference: { 396 | wkid: 4326 397 | } 398 | }); 399 | }); 400 | 401 | test('should convert a GeoJSON MultiLineString to an ArcGIS Polyline and include z values', function (t) { 402 | t.plan(1); 403 | 404 | const input = { 405 | type: 'MultiLineString', 406 | coordinates: [ 407 | [ 408 | [41.8359375, 71.015625, 10], 409 | [56.953125, 33.75, 15] 410 | ], 411 | [ 412 | [21.796875, 36.5625, 12], 413 | [47.8359375, 71.015625, 10] 414 | ] 415 | ] 416 | }; 417 | 418 | var output = geojsonToArcGIS(input); 419 | 420 | t.deepEqual(output, { 421 | paths: [ 422 | [ 423 | [41.8359375, 71.015625, 10], 424 | [56.953125, 33.75, 15] 425 | ], 426 | [ 427 | [21.796875, 36.5625, 12], 428 | [47.8359375, 71.015625, 10] 429 | ] 430 | ], 431 | hasZ: true, 432 | spatialReference: { 433 | wkid: 4326 434 | } 435 | }); 436 | }); 437 | 438 | test('should convert a GeoJSON MultiPolygon to an ArcGIS Polygon', function (t) { 439 | t.plan(1); 440 | 441 | const input = { 442 | type: 'MultiPolygon', 443 | coordinates: [ 444 | [ 445 | [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]] 446 | ], 447 | [ 448 | [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] 449 | ] 450 | ] 451 | }; 452 | 453 | var output = geojsonToArcGIS(input); 454 | 455 | t.deepEqual(output, { 456 | rings: [ 457 | [[102, 2], [102, 3], [103, 3], [103, 2], [102, 2]], 458 | [[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]] 459 | ], 460 | spatialReference: { 461 | wkid: 4326 462 | } 463 | }); 464 | }); 465 | 466 | test('should convert a GeoJSON MultiPolygon to an ArcGIS Polygon and include z values', function (t) { 467 | t.plan(1); 468 | 469 | const input = { 470 | type: 'MultiPolygon', 471 | coordinates: [ 472 | [ 473 | [[102.0, 2.0, 10], [103.0, 2.0, 10], [103.0, 3.0, 10], [102.0, 3.0, 10], [102.0, 2.0, 10]] 474 | ], 475 | [ 476 | [[100.0, 0.0, 15], [101.0, 0.0, 15], [101.0, 1.0, 15], [100.0, 1.0, 15], [100.0, 0.0, 15]] 477 | ] 478 | ] 479 | }; 480 | 481 | var output = geojsonToArcGIS(input); 482 | 483 | t.deepEqual(output, { 484 | rings: [ 485 | [[102, 2, 10], [102, 3, 10], [103, 3, 10], [103, 2, 10], [102, 2, 10]], 486 | [[100, 0, 15], [100, 1, 15], [101, 1, 15], [101, 0, 15], [100, 0, 15]] 487 | ], 488 | hasZ: true, 489 | spatialReference: { 490 | wkid: 4326 491 | } 492 | }); 493 | }); 494 | 495 | test('should convert a GeoJSON MultiPolygon w/ holes to an ArcGIS Polygon', function (t) { 496 | t.plan(1); 497 | 498 | const input = { 499 | type: 'MultiPolygon', 500 | coordinates: [ 501 | [ 502 | [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]] 503 | ], 504 | [ 505 | [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], 506 | [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]] 507 | ] 508 | ] 509 | }; 510 | 511 | var output = geojsonToArcGIS(input); 512 | t.deepEqual(output, { 513 | spatialReference: { 514 | wkid: 4326 515 | }, 516 | rings: [ 517 | [ 518 | [102, 2], 519 | [102, 3], 520 | [103, 3], 521 | [103, 2], 522 | [102, 2] 523 | ], 524 | [ 525 | [100.2, 0.2], 526 | [100.8, 0.2], 527 | [100.8, 0.8], 528 | [100.2, 0.8], 529 | [100.2, 0.2]], 530 | [ 531 | [100, 0], 532 | [100, 1], 533 | [101, 1], 534 | [101, 0], 535 | [100, 0] 536 | ] 537 | ] 538 | }); 539 | }); 540 | 541 | test('should close rings when converting a GeoJSON MultiPolygon w/ holes to an ArcGIS Polygon', function (t) { 542 | t.plan(1); 543 | 544 | const input = { 545 | type: 'MultiPolygon', 546 | coordinates: [ 547 | [ 548 | [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0]] 549 | ], 550 | [ 551 | [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0]], 552 | [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8]] 553 | ] 554 | ] 555 | }; 556 | 557 | var output = geojsonToArcGIS(input); 558 | t.deepEqual(output, { 559 | spatialReference: { 560 | wkid: 4326 561 | }, 562 | rings: [ 563 | [ 564 | [102, 2], 565 | [102, 3], 566 | [103, 3], 567 | [103, 2], 568 | [102, 2] 569 | ], 570 | [ 571 | [100.2, 0.2], 572 | [100.8, 0.2], 573 | [100.8, 0.8], 574 | [100.2, 0.8], 575 | [100.2, 0.2] 576 | ], 577 | [ 578 | [100, 0], 579 | [100, 1], 580 | [101, 1], 581 | [101, 0], 582 | [100, 0] 583 | ] 584 | ] 585 | }); 586 | }); 587 | 588 | test('should convert a GeoJSON Feature into an ArcGIS Feature', function (t) { 589 | t.plan(1); 590 | 591 | const input = { 592 | type: 'Feature', 593 | id: 'foo', 594 | geometry: { 595 | type: 'Polygon', 596 | coordinates: [ 597 | [ 598 | [41.8359375, 71.015625], 599 | [56.953125, 33.75], 600 | [21.796875, 36.5625], 601 | [41.8359375, 71.015625] 602 | ] 603 | ] 604 | }, 605 | properties: { 606 | foo: 'bar' 607 | } 608 | }; 609 | 610 | var output = geojsonToArcGIS(input); 611 | 612 | t.deepEqual(output, { 613 | geometry: { 614 | rings: [ 615 | [ 616 | [41.8359375, 71.015625], 617 | [56.953125, 33.75], 618 | [21.796875, 36.5625], 619 | [41.8359375, 71.015625] 620 | ] 621 | ], 622 | spatialReference: { 623 | wkid: 4326 624 | } 625 | }, 626 | attributes: { 627 | foo: 'bar', 628 | OBJECTID: 'foo' 629 | } 630 | }); 631 | }); 632 | 633 | test('should convert a GeoJSON Feature into an ArcGIS Feature w/ a custom id', function (t) { 634 | t.plan(1); 635 | 636 | const input = { 637 | type: 'Feature', 638 | id: 'foo', 639 | geometry: { 640 | type: 'Polygon', 641 | coordinates: [ 642 | [[41.8359375, 71.015625], 643 | [56.953125, 33.75], 644 | [21.796875, 36.5625], 645 | [41.8359375, 71.015625]] 646 | ] 647 | }, 648 | properties: { 649 | foo: 'bar' 650 | } 651 | }; 652 | 653 | var output = geojsonToArcGIS(input, 'myId'); 654 | 655 | t.deepEqual(output, { 656 | geometry: { 657 | rings: [ 658 | [ 659 | [41.8359375, 71.015625], 660 | [56.953125, 33.75], 661 | [21.796875, 36.5625], 662 | [41.8359375, 71.015625] 663 | ] 664 | ], 665 | spatialReference: { 666 | wkid: 4326 667 | } 668 | }, 669 | attributes: { 670 | foo: 'bar', 671 | myId: 'foo' 672 | } 673 | }); 674 | }); 675 | 676 | test('should allow converting a GeoJSON Feature to an ArcGIS Feature with no properties or geometry', function (t) { 677 | t.plan(1); 678 | 679 | const input = { 680 | type: 'Feature', 681 | id: 'foo', 682 | geometry: null, 683 | properties: null 684 | }; 685 | 686 | var output = geojsonToArcGIS(input); 687 | 688 | t.deepEqual(output, { 689 | attributes: { 690 | OBJECTID: 'foo' 691 | } 692 | }); 693 | }); 694 | 695 | test('should convert a GeoJSON FeatureCollection into an array of ArcGIS Feature JSON', function (t) { 696 | t.plan(1); 697 | 698 | const input = { 699 | type: 'FeatureCollection', 700 | features: [{ 701 | type: 'Feature', 702 | geometry: { 703 | type: 'Point', 704 | coordinates: [102.0, 0.5] 705 | }, 706 | properties: { 707 | prop0: 'value0' 708 | } 709 | }, { 710 | type: 'Feature', 711 | geometry: { 712 | type: 'LineString', 713 | coordinates: [ 714 | [102.0, 0.0], 715 | [103.0, 1.0], 716 | [104.0, 0.0], 717 | [105.0, 1.0] 718 | ] 719 | }, 720 | properties: { 721 | prop0: 'value0' 722 | } 723 | }, { 724 | type: 'Feature', 725 | geometry: { 726 | type: 'Polygon', 727 | coordinates: [ 728 | [[100.0, 0.0], 729 | [101.0, 0.0], 730 | [101.0, 1.0], 731 | [100.0, 1.0], 732 | [100.0, 0.0]] 733 | ] 734 | }, 735 | properties: { 736 | prop0: 'value0' 737 | } 738 | }] 739 | }; 740 | 741 | var output = geojsonToArcGIS(input); 742 | 743 | t.deepEqual(output, [{ 744 | geometry: { 745 | x: 102, 746 | y: 0.5, 747 | spatialReference: { 748 | wkid: 4326 749 | } 750 | }, 751 | attributes: { 752 | prop0: 'value0' 753 | } 754 | }, { 755 | geometry: { 756 | paths: [ 757 | [[102, 0], 758 | [103, 1], 759 | [104, 0], 760 | [105, 1]] 761 | ], 762 | spatialReference: { 763 | wkid: 4326 764 | } 765 | }, 766 | attributes: { 767 | prop0: 'value0' 768 | } 769 | }, { 770 | geometry: { 771 | rings: [ 772 | [[100, 0], 773 | [100, 1], 774 | [101, 1], 775 | [101, 0], 776 | [100, 0]] 777 | ], 778 | spatialReference: { 779 | wkid: 4326 780 | } 781 | }, 782 | attributes: { 783 | prop0: 'value0' 784 | } 785 | }]); 786 | }); 787 | 788 | test('should convert a GeoJSON GeometryCollection into an array of ArcGIS Geometries', function (t) { 789 | t.plan(1); 790 | 791 | const input = { 792 | type: 'GeometryCollection', 793 | geometries: [{ 794 | type: 'Polygon', 795 | coordinates: [[[-95, 43], [-95, 50], [-90, 50], [-91, 42], [-95, 43]]] 796 | }, { 797 | type: 'LineString', 798 | coordinates: [[-89, 42], [-89, 50], [-80, 50], [-80, 42]] 799 | }, { 800 | type: 'Point', 801 | coordinates: [-94, 46] 802 | }] 803 | }; 804 | 805 | var output = geojsonToArcGIS(input); 806 | 807 | t.deepEqual(output, [{ 808 | rings: [ 809 | [[-95, 43], 810 | [-95, 50], 811 | [-90, 50], 812 | [-91, 42], 813 | [-95, 43]] 814 | ], 815 | spatialReference: { 816 | wkid: 4326 817 | } 818 | }, { 819 | paths: [ 820 | [[-89, 42], 821 | [-89, 50], 822 | [-80, 50], 823 | [-80, 42]] 824 | ], 825 | spatialReference: { 826 | wkid: 4326 827 | } 828 | }, { 829 | x: -94, 830 | y: 46, 831 | spatialReference: { 832 | wkid: 4326 833 | } 834 | }]); 835 | }); 836 | 837 | test('should not modify the original GeoJSON object', function (t) { 838 | t.plan(1); 839 | 840 | var geojson = { 841 | type: 'FeatureCollection', 842 | features: [{ 843 | type: 'Feature', 844 | geometry: { 845 | type: 'Point', 846 | coordinates: [102.0, 0.5] 847 | }, 848 | properties: { 849 | prop0: 'value0' 850 | } 851 | }, { 852 | type: 'Feature', 853 | geometry: { 854 | type: 'LineString', 855 | coordinates: [ 856 | [102.0, 0.0], 857 | [103.0, 1.0], 858 | [104.0, 0.0], 859 | [105.0, 1.0] 860 | ] 861 | }, 862 | properties: { 863 | prop0: 'value0' 864 | } 865 | }, { 866 | type: 'Feature', 867 | geometry: { 868 | type: 'Polygon', 869 | coordinates: [ 870 | [[100.0, 0.0], 871 | [101.0, 0.0], 872 | [101.0, 1.0], 873 | [100.0, 1.0], 874 | [100.0, 0.0]] 875 | ] 876 | }, 877 | properties: { 878 | prop0: 'value0' 879 | } 880 | }] 881 | }; 882 | 883 | const original = JSON.stringify(geojson); 884 | 885 | geojsonToArcGIS(geojson); 886 | 887 | t.deepEqual(original, JSON.stringify(geojson)); 888 | }); 889 | -------------------------------------------------------------------------------- /packages/common/README.md: -------------------------------------------------------------------------------- 1 | # @terraformer/common 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | [![standard][standard-image]][standard-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/@terraformer/common.svg?style=flat-square 8 | [npm-url]: https://www.npmjs.com/package/@terraformer/common 9 | [travis-image]: https://app.travis-ci.com/terraformer-js/terraformer.svg?branch=main 10 | [travis-url]: https://app.travis-ci.com/terraformer-js/terraformer 11 | [standard-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square 12 | [standard-url]: http://npm.im/semistandard 13 | 14 | > Shared Terraformer utility methods. 15 | 16 | ## [Contributing](./CONTRIBUTING.md) 17 | 18 | ## [LICENSE](https://raw.githubusercontent.com/terraformer-js/terraformer/master/LICENSE) 19 | -------------------------------------------------------------------------------- /packages/common/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | const isNumber = (n) => !isNaN(parseFloat(n)) && isFinite(n); 5 | 6 | const edgeIntersectsEdge = (a1, a2, b1, b2) => { 7 | var uaT = (b2[0] - b1[0]) * (a1[1] - b1[1]) - (b2[1] - b1[1]) * (a1[0] - b1[0]); 8 | var ubT = (a2[0] - a1[0]) * (a1[1] - b1[1]) - (a2[1] - a1[1]) * (a1[0] - b1[0]); 9 | var uB = (b2[1] - b1[1]) * (a2[0] - a1[0]) - (b2[0] - b1[0]) * (a2[1] - a1[1]); 10 | 11 | if (uB !== 0) { 12 | var ua = uaT / uB; 13 | var ub = ubT / uB; 14 | 15 | if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { 16 | return true; 17 | } 18 | } 19 | 20 | return false; 21 | }; 22 | 23 | export const arraysIntersectArrays = (a, b) => { 24 | if (isNumber(a[0][0])) { 25 | if (isNumber(b[0][0])) { 26 | for (var i = 0; i < a.length - 1; i++) { 27 | for (var j = 0; j < b.length - 1; j++) { 28 | if (edgeIntersectsEdge(a[i], a[i + 1], b[j], b[j + 1])) { 29 | return true; 30 | } 31 | } 32 | } 33 | } else { 34 | for (var k = 0; k < b.length; k++) { 35 | if (arraysIntersectArrays(a, b[k])) { 36 | return true; 37 | } 38 | } 39 | } 40 | } else { 41 | for (var l = 0; l < a.length; l++) { 42 | if (arraysIntersectArrays(a[l], b)) { 43 | return true; 44 | } 45 | } 46 | } 47 | return false; 48 | }; 49 | 50 | export const coordinatesContainPoint = (coordinates, point) => { 51 | let contains = false; 52 | for (let i = -1, l = coordinates.length, j = l - 1; ++i < l; j = i) { 53 | if (((coordinates[i][1] <= point[1] && point[1] < coordinates[j][1]) || 54 | (coordinates[j][1] <= point[1] && point[1] < coordinates[i][1])) && 55 | (point[0] < (coordinates[j][0] - coordinates[i][0]) * (point[1] - coordinates[i][1]) / (coordinates[j][1] - coordinates[i][1]) + coordinates[i][0])) { 56 | contains = !contains; 57 | } 58 | } 59 | return contains; 60 | }; 61 | 62 | export const pointsEqual = (a, b) => { 63 | for (let i = 0; i < a.length; i++) { 64 | if (a[i] !== b[i]) { 65 | return false; 66 | } 67 | } 68 | 69 | return true; 70 | }; 71 | 72 | export const arrayIntersectsArray = (a, b) => { 73 | for (let i = 0; i < a.length - 1; i++) { 74 | for (let j = 0; j < b.length - 1; j++) { 75 | if (edgeIntersectsEdge(a[i], a[i + 1], b[j], b[j + 1])) { 76 | return true; 77 | } 78 | } 79 | } 80 | return false; 81 | }; 82 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terraformer/common", 3 | "description": "Shared @terraformer utility methods.", 4 | "version": "2.1.2", 5 | "author": "John Gravois ", 6 | "bugs": { 7 | "url": "https://github.com/terraformer-js/terraformer/issues" 8 | }, 9 | "contributors": [ 10 | "John Gravois " 11 | ], 12 | "files": [ 13 | "index.js" 14 | ], 15 | "homepage": "https://github.com/terraformer-js/terraformer", 16 | "keywords": [ 17 | "arcgis", 18 | "convert", 19 | "geo", 20 | "geojson", 21 | "geometry" 22 | ], 23 | "license": "MIT", 24 | "main": "index.js", 25 | "module": "index.js", 26 | "unpkg": "index.js", 27 | "jsdelivr": "index.js", 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/terraformer-js/terraformer" 31 | }, 32 | "scripts": {}, 33 | "browserify": { 34 | "transform": [ 35 | [ 36 | "babelify", 37 | { 38 | "presets": [ 39 | "@babel/preset-env" 40 | ] 41 | } 42 | ] 43 | ] 44 | }, 45 | "publishConfig": { 46 | "access": "public" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/common/test/index.test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | import { arraysIntersectArrays } from '../index.js'; 3 | 4 | test('should exist', function (t) { 5 | t.plan(1); 6 | t.ok(arraysIntersectArrays); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/spatial/README.hbs: -------------------------------------------------------------------------------- 1 | # @terraformer/spatial 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | [![standard][standard-image]][standard-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/@terraformer/spatial.svg?style=flat-square 8 | [npm-url]: https://www.npmjs.com/package/@terraformer/spatial 9 | [travis-image]: https://app.travis-ci.com/terraformer-js/terraformer.svg?branch=main 10 | [travis-url]: https://app.travis-ci.com/terraformer-js/terraformer 11 | [standard-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square 12 | [standard-url]: http://npm.im/semistandard 13 | 14 | > Spatial predicates for GeoJSON. 15 | 16 | ## Install 17 | 18 | ``` 19 | npm install @terraformer/spatial 20 | ``` 21 | 22 | ## API Reference 23 | 24 | {{>main}} 25 | * * * 26 | 27 | ## Usage 28 | 29 | ### Browser (from CDN) 30 | 31 | This package is distributed as a [UMD](https://github.com/umdjs/umd) module and can also be used in AMD based systems or as a global under the `Terraformer` namespace. 32 | 33 | ```html 34 | 35 | ``` 36 | ```js 37 | const input = { 38 | 'type': 'Polygon', 39 | 'coordinates': [ 40 | [ [41.83, 71.01], [56.95, 33.75], [21.79, 36.56], [41.83, 71.01] ] 41 | ] 42 | }; 43 | 44 | Terraformer.isConvex(input.coordinates[0]); // true 45 | ``` 46 | 47 | ### Node.js 48 | 49 | ```js 50 | const Terraformer = require('@terraformer/spatial'); 51 | 52 | Terraformer.isConvex(/* ... */); 53 | ``` 54 | 55 | ### ES module in the browser 56 | 57 | ```html 58 | 64 | ``` 65 | 66 | ## FAQ 67 | 68 |
69 | What's the difference between this project and Turf.js? 70 | 71 | Both libraries work with GeoJSON and share many similar functions. Turf.js relies on JSTS, and some folks have [found it to be slower](https://github.com/Esri/terraformer/issues/268#issuecomment-196413416). In the past Turf.js did not include predicates like 'within', 'contains' and 'intersects', but that no longer appears to be the case. 72 |
73 | 74 | ## [Contributing](./CONTRIBUTING.md) 75 | 76 | ## [LICENSE](https://raw.githubusercontent.com/terraformer-js/terraformer/master/LICENSE) 77 | -------------------------------------------------------------------------------- /packages/spatial/README.md: -------------------------------------------------------------------------------- 1 | # @terraformer/spatial 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | [![standard][standard-image]][standard-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/@terraformer/spatial.svg?style=flat-square 8 | [npm-url]: https://www.npmjs.com/package/@terraformer/spatial 9 | [travis-image]: https://app.travis-ci.com/terraformer-js/terraformer.svg?branch=main 10 | [travis-url]: https://app.travis-ci.com/terraformer-js/terraformer 11 | [standard-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square 12 | [standard-url]: http://npm.im/semistandard 13 | 14 | > Spatial predicates for GeoJSON. 15 | 16 | ## Install 17 | 18 | ``` 19 | npm install @terraformer/spatial 20 | ``` 21 | 22 | ## API Reference 23 | 24 | 25 | 26 | ## Terraformer 27 | 28 | * [Terraformer](#module_Terraformer) 29 | * [.MercatorCRS](#module_Terraformer.MercatorCRS) 30 | * [.GeographicCRS](#module_Terraformer.GeographicCRS) 31 | * [.applyConverter(GeoJSON, function)](#module_Terraformer.applyConverter) ⇒ object 32 | * [.calculateBounds(GeoJSON)](#module_Terraformer.calculateBounds) ⇒ Array.<Number> 33 | * [.calculateEnvelope(GeoJSON)](#module_Terraformer.calculateEnvelope) ⇒ Object 34 | * [.positionToGeographic(CoordinatePair)](#module_Terraformer.positionToGeographic) ⇒ Array.<Number, Number> 35 | * [.positionToMercator(CoordinatePair)](#module_Terraformer.positionToMercator) ⇒ Array.<Number, Number> 36 | * [.toMercator(GeoJSON)](#module_Terraformer.toMercator) ⇒ object 37 | * [.convexHull(GeoJSON)](#module_Terraformer.convexHull) ⇒ Array.<Coordinates> 38 | * [.isConvex(GeoJSON)](#module_Terraformer.isConvex) ⇒ Boolean 39 | * [.polygonContainsPoint(GeoJSON, GeoJSON)](#module_Terraformer.polygonContainsPoint) ⇒ Boolean 40 | * [.within(GeoJSON, GeoJSON)](#module_Terraformer.within) ⇒ Boolean 41 | * [.contains(GeoJSON, GeoJSON)](#module_Terraformer.contains) ⇒ Boolean 42 | * [.intersects(GeoJSON, GeoJSON)](#module_Terraformer.intersects) ⇒ Boolean 43 | * [.toCircle(CoordinatePair, [Radius], [Steps])](#module_Terraformer.toCircle) ⇒ object 44 | * [.toGeographic(GeoJSON)](#module_Terraformer.toGeographic) ⇒ object 45 | 46 | 47 | 48 | ### Terraformer.MercatorCRS 49 | WKID [3857](https://epsg.io/3857) 50 | 51 | **Kind**: static constant of [Terraformer](#module_Terraformer) 52 | 53 | 54 | ### Terraformer.GeographicCRS 55 | WKID [4326](https://epsg.io/4326) 56 | 57 | **Kind**: static constant of [Terraformer](#module_Terraformer) 58 | 59 | 60 | ### Terraformer.applyConverter(GeoJSON, function) ⇒ object 61 | Runs the passed function against every Coordinate in the geojson object. 62 | 63 | **Kind**: static method of [Terraformer](#module_Terraformer) 64 | **Returns**: object - GeoJSON - [GeoJSON](https://tools.ietf.org/html/rfc7946) with altered coordinates. 65 | ```js 66 | import { applyConverter } from "@terraformer/spatial" 67 | 68 | applyConverter({ 69 | type: "Point", 70 | coordinates: [ 45, 60 ] 71 | }, (coord) => [coord[0] + 1, coord[1] - 1]) 72 | 73 | >> { type: "Point", coordinates: [ 46, 59 ] } 74 | ``` 75 | 76 | | Param | Type | Description | 77 | | --- | --- | --- | 78 | | GeoJSON | object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 79 | | function | function | Your function will be passed a Coordinate and will be expected to return a Coordinate. | 80 | 81 | 82 | 83 | ### Terraformer.calculateBounds(GeoJSON) ⇒ Array.<Number> 84 | Calculate the bounding box of the input. 85 | 86 | **Kind**: static method of [Terraformer](#module_Terraformer) 87 | **Returns**: Array.<Number> - [ xmin, ymin, xmax, ymax ]. 88 | ```js 89 | import { calculateBounds } from "@terraformer/spatial" 90 | 91 | calculateBounds({ 92 | type: "Point", 93 | coordinates: [ 45, 60 ] 94 | }) 95 | 96 | >> [45, 60, 45, 60] 97 | ``` 98 | 99 | | Param | Type | Description | 100 | | --- | --- | --- | 101 | | GeoJSON | object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 102 | 103 | 104 | 105 | ### Terraformer.calculateEnvelope(GeoJSON) ⇒ Object 106 | Calculate the envelope surrounding the input. 107 | 108 | **Kind**: static method of [Terraformer](#module_Terraformer) 109 | **Returns**: Object - Object in the form { x, y, w, h }. 110 | ```js 111 | import { calculateEnvelope } from "@terraformer/spatial" 112 | 113 | calculateEnvelope({ 114 | type: "Point", 115 | coordinates: [ 100, 100 ] 116 | }) 117 | 118 | >> { x: 100, y: 100, w: 0, h: 0, } 119 | ``` 120 | 121 | | Param | Type | Description | 122 | | --- | --- | --- | 123 | | GeoJSON | object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 124 | 125 | 126 | 127 | ### Terraformer.positionToGeographic(CoordinatePair) ⇒ Array.<Number, Number> 128 | Reprojects the passed Coordinate pair to WGS84 (4326) spatial reference. 129 | 130 | **Kind**: static method of [Terraformer](#module_Terraformer) 131 | **Returns**: Array.<Number, Number> - CoordinatePair. 132 | ```js 133 | import { positionToGeographic } from "@terraformer/spatial" 134 | 135 | positionToGeographic([ -13580978, 5621521 ]) // [ 45, 60 ] 136 | ``` 137 | 138 | | Param | Type | Description | 139 | | --- | --- | --- | 140 | | CoordinatePair | Array.<Number, Number> | An X,Y position. | 141 | 142 | 143 | 144 | ### Terraformer.positionToMercator(CoordinatePair) ⇒ Array.<Number, Number> 145 | Reprojects the passed Coordinate pair to web mercator (3857) spatial reference. 146 | 147 | **Kind**: static method of [Terraformer](#module_Terraformer) 148 | **Returns**: Array.<Number, Number> - CoordinatePair. 149 | ```js 150 | import { positionToGeographic } from "@terraformer/spatial" 151 | 152 | positionToMercator([ 45, 60 ]) // [ -13580978, 5621521 ] 153 | ``` 154 | 155 | | Param | Type | Description | 156 | | --- | --- | --- | 157 | | CoordinatePair | Array.<Number, Number> | An X,Y position. | 158 | 159 | 160 | 161 | ### Terraformer.toMercator(GeoJSON) ⇒ object 162 | Reproject WGS84 (Lat/Lng) GeoJSON to Web Mercator. 163 | 164 | **Kind**: static method of [Terraformer](#module_Terraformer) 165 | **Returns**: object - GeoJSON 166 | ```js 167 | import { toMercator } from "@terraformer/spatial" 168 | 169 | toMercator({ 170 | type: "Point", 171 | coordinates: [ 45, 60 ] 172 | }) 173 | 174 | >> { type: "Point", coordinates: [ -13580978, 5621521 ], crs } 175 | ``` 176 | 177 | | Param | Type | Description | 178 | | --- | --- | --- | 179 | | GeoJSON | object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 180 | 181 | 182 | 183 | ### Terraformer.convexHull(GeoJSON) ⇒ Array.<Coordinates> 184 | Calculate the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of GeoJSON input. 185 | 186 | **Kind**: static method of [Terraformer](#module_Terraformer) 187 | **Returns**: Array.<Coordinates> - An array of GeoJSON coordinates representing the convex hull of the input GeoJSON. 188 | ```js 189 | import { convexHull } from "@terraformer/spatial" 190 | 191 | convexHull({ 192 | type: "LineString", 193 | coordinates: [ 194 | [ 100, 0 ], [ -45, 122 ], [ 80, -60 ] 195 | ] 196 | }) 197 | 198 | >> 199 | { 200 | type: "Polygon", 201 | coordinates: [ 202 | [ [ 100, 0 ], [ -45, 122 ], [ 80, -60 ], [ 100, 0 ] ] 203 | ] 204 | } 205 | ``` 206 | 207 | | Param | Type | Description | 208 | | --- | --- | --- | 209 | | GeoJSON | object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 210 | 211 | 212 | 213 | ### Terraformer.isConvex(GeoJSON) ⇒ Boolean 214 | Determine whether input GeoJSON has a [convex](https://en.wikipedia.org/wiki/Convex_set) shape. 215 | 216 | **Kind**: static method of [Terraformer](#module_Terraformer) 217 | **Returns**: Boolean - Yes/No 218 | ```js 219 | import { isConvex } from "@terraformer/spatial" 220 | 221 | isConvex({ 222 | type: "Polygon", 223 | coordinates: [ 224 | [ [ 100, 0 ], [ -45, 122 ], [ 80, -60 ], [ 100, 0 ] ] 225 | ] 226 | }) 227 | 228 | >> true 229 | ``` 230 | 231 | | Param | Type | Description | 232 | | --- | --- | --- | 233 | | GeoJSON | Object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 234 | 235 | 236 | 237 | ### Terraformer.polygonContainsPoint(GeoJSON, GeoJSON) ⇒ Boolean 238 | Accepts the geometry of a polygon and point and returns `true` if the point falls within the polygon. 239 | 240 | **Kind**: static method of [Terraformer](#module_Terraformer) 241 | **Returns**: Boolean - Yes/No 242 | ```js 243 | import { polygonContainsPoint } from "@terraformer/spatial" 244 | 245 | polygonContainsPoint( 246 | [ 247 | [ [ 1, 2 ], [ 2, 2 ], [ 2, 1 ], [ 1, 1 ], [ 1, 2 ] ] 248 | ], 249 | [ 10, 10 ] 250 | ) 251 | 252 | >> false 253 | ``` 254 | 255 | | Param | Type | Description | 256 | | --- | --- | --- | 257 | | GeoJSON | Object | [GeoJSON Polygon](https://tools.ietf.org/html/rfc7946#section-3.1.6) coordinates. | 258 | | GeoJSON | Object | [GeoJSON Point](https://tools.ietf.org/html/rfc7946#section-3.1.2) coordinates. | 259 | 260 | 261 | 262 | ### Terraformer.within(GeoJSON, GeoJSON) ⇒ Boolean 263 | Returns `true` if the GeoJSON passed as the first argument is completely inside the GeoJSON object passed in the second position. 264 | 265 | **Kind**: static method of [Terraformer](#module_Terraformer) 266 | **Returns**: Boolean - Yes/No 267 | ```js 268 | import { within } from "@terraformer/spatial" 269 | 270 | within({ 271 | type: "Point", 272 | coordinates: [ 10, 10 ] 273 | }, 274 | { 275 | type: "Polygon", 276 | coordinates: [ 277 | [ [ 5, 5 ], [ 5, 15 ], [ 15, 15 ], [ 15, 5 ], [ 5, 5 ] ] 278 | ] 279 | }) 280 | 281 | >> true 282 | ``` 283 | 284 | | Param | Type | Description | 285 | | --- | --- | --- | 286 | | GeoJSON | Object | [GeoJSON](https://tools.ietf.org/html) that may be within the second input. | 287 | | GeoJSON | Object | [GeoJSON](https://tools.ietf.org/html/rfc7946#section-3.1.2) that may contain the first input. | 288 | 289 | 290 | 291 | ### Terraformer.contains(GeoJSON, GeoJSON) ⇒ Boolean 292 | Returns `true` if the GeoJSON passed as the second argument is completely inside the GeoJSON object passed in the first position. 293 | 294 | **Kind**: static method of [Terraformer](#module_Terraformer) 295 | **Returns**: Boolean - Yes/No 296 | ```js 297 | import { contains } from "@terraformer/spatial" 298 | 299 | contains({ 300 | type: "Polygon", 301 | coordinates: [ 302 | [ [ 5, 5 ], [ 5, 15 ], [ 15, 15 ], [ 15, 5 ], [ 5, 5 ] ] 303 | ]}, 304 | { 305 | type: "Point", 306 | coordinates: [ 10, 10 ] 307 | }) 308 | 309 | >> true 310 | ``` 311 | 312 | | Param | Type | Description | 313 | | --- | --- | --- | 314 | | GeoJSON | Object | [GeoJSON](https://tools.ietf.org/html) that may contain the second input. | 315 | | GeoJSON | Object | [GeoJSON](https://tools.ietf.org/html/rfc7946#section-3.1.2) that may be contained by the first input. | 316 | 317 | 318 | 319 | ### Terraformer.intersects(GeoJSON, GeoJSON) ⇒ Boolean 320 | Returns `true` if the two input GeoJSON objects intersect one another. 321 | 322 | **Kind**: static method of [Terraformer](#module_Terraformer) 323 | **Returns**: Boolean - Yes/No 324 | ```js 325 | import { intersects } from "@terraformer/spatial" 326 | 327 | intersects({ 328 | type: "Point", 329 | coordinates: [ 10, 10 ] 330 | }, 331 | { 332 | type: "Polygon", 333 | coordinates: [ 334 | [ [ 5, 5 ], [ 5, 15 ], [ 15, 15 ], [ 15, 5 ], [ 5, 5 ] ] 335 | ] 336 | }) 337 | 338 | >> true 339 | ``` 340 | 341 | | Param | Type | Description | 342 | | --- | --- | --- | 343 | | GeoJSON | Object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 344 | | GeoJSON | Object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 345 | 346 | 347 | 348 | ### Terraformer.toCircle(CoordinatePair, [Radius], [Steps]) ⇒ object 349 | Uses an input Coordinate pair to create a GeoJSON Feature containing a Polygon representing a circle with a discrete number of sides. 350 | 351 | **Kind**: static method of [Terraformer](#module_Terraformer) 352 | **Returns**: object - GeoJSON 353 | ```js 354 | import { toCircle } from "@terraformer/spatial" 355 | 356 | toCircle([ -118, 34 ], 500) 357 | 358 | >> { type: "Feature", geometry: { type: "Polygon"}, coordinates: [...] } 359 | ``` 360 | 361 | | Param | Type | Default | Description | 362 | | --- | --- | --- | --- | 363 | | CoordinatePair | Array.<Number, Number> | | A GeoJSON Coordinate in `[x,y]` format. | 364 | | [Radius] | Number | 250 | The radius of the circle (in meters). | 365 | | [Steps] | Number | 64 | The number of sides the output polygon will contain. | 366 | 367 | 368 | 369 | ### Terraformer.toGeographic(GeoJSON) ⇒ object 370 | Reproject Web Mercator GeoJSON to WGS84 (Lat/Long). 371 | 372 | **Kind**: static method of [Terraformer](#module_Terraformer) 373 | **Returns**: object - GeoJSON 374 | ```js 375 | import { toGeographic } from "@terraformer/spatial" 376 | 377 | toGeographic({ 378 | type: "Point", 379 | coordinates: [ -13580978, 5621521 ] 380 | }) 381 | 382 | >> { type: "Point", coordinates: [ 45, 60 ] } 383 | ``` 384 | 385 | | Param | Type | Description | 386 | | --- | --- | --- | 387 | | GeoJSON | object | The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. | 388 | 389 | * * * 390 | 391 | ## Usage 392 | 393 | ### Browser (from CDN) 394 | 395 | This package is distributed as a [UMD](https://github.com/umdjs/umd) module and can also be used in AMD based systems or as a global under the `Terraformer` namespace. 396 | 397 | ```html 398 | 399 | ``` 400 | ```js 401 | const input = { 402 | 'type': 'Polygon', 403 | 'coordinates': [ 404 | [ [41.83, 71.01], [56.95, 33.75], [21.79, 36.56], [41.83, 71.01] ] 405 | ] 406 | }; 407 | 408 | Terraformer.isConvex(input.coordinates[0]); // true 409 | ``` 410 | 411 | ### Node.js 412 | 413 | ```js 414 | const Terraformer = require('@terraformer/spatial'); 415 | 416 | Terraformer.isConvex(/* ... */); 417 | ``` 418 | 419 | ### ES module in the browser 420 | 421 | ```html 422 | 428 | ``` 429 | 430 | ## FAQ 431 | 432 |
433 | What's the difference between this project and Turf.js? 434 | 435 | Both libraries work with GeoJSON and share many similar functions. Turf.js relies on JSTS, and some folks have [found it to be slower](https://github.com/Esri/terraformer/issues/268#issuecomment-196413416). In the past Turf.js did not include predicates like 'within', 'contains' and 'intersects', but that no longer appears to be the case. 436 |
437 | 438 | ## [Contributing](./CONTRIBUTING.md) 439 | 440 | ## [LICENSE](https://raw.githubusercontent.com/terraformer-js/terraformer/master/LICENSE) 441 | -------------------------------------------------------------------------------- /packages/spatial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terraformer/spatial", 3 | "description": "Spatial predicates for GeoJSON.", 4 | "version": "2.2.1", 5 | "author": "Patrick Arlt ", 6 | "bugs": { 7 | "url": "https://github.com/terraformer-js/terraformer/issues" 8 | }, 9 | "contributors": [ 10 | "Patrick Arlt (http://patrickarlt.com)", 11 | "Jerry Sievert (http://legitimatesounding.com)", 12 | "John Gravois " 13 | ], 14 | "dependencies": { 15 | "@terraformer/common": "^2.1.2" 16 | }, 17 | "files": [ 18 | "dist/**" 19 | ], 20 | "homepage": "https://github.com/terraformer-js/terraformer", 21 | "keywords": [ 22 | "arcgis", 23 | "convert", 24 | "geo", 25 | "geojson", 26 | "geometry" 27 | ], 28 | "license": "MIT", 29 | "main": "dist/t-spatial.umd.js", 30 | "unpkg": "dist/t-spatial.umd.js", 31 | "jsdelivr": "dist/t-spatial.umd.js", 32 | "module": "dist/t-spatial.esm.js", 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/terraformer-js/terraformer" 36 | }, 37 | "scripts": { 38 | "build": "rollup -c ../../rollup.umd.config.js && rollup -c ../../rollup.esm.config.js", 39 | "doc": "jsdoc2md --files src/index.js --template README.hbs > README.md" 40 | }, 41 | "publishConfig": { 42 | "access": "public" 43 | }, 44 | "gitHead": "8fb60ae0c5a13fb7f31fc29d028d889b22091bfa" 45 | } 46 | -------------------------------------------------------------------------------- /packages/spatial/src/bounds.js: -------------------------------------------------------------------------------- 1 | /* 2 | Internal: Calculate an bounding box from an nested array of positions 3 | [ 4 | [ 5 | [ [lng, lat],[lng, lat],[lng, lat] ] 6 | ] 7 | [ 8 | [lng, lat],[lng, lat],[lng, lat] 9 | ] 10 | [ 11 | [lng, lat],[lng, lat],[lng, lat] 12 | ] 13 | ] 14 | */ 15 | const calculateBoundsFromNestedArrays = (array) => { 16 | let x1 = null; let x2 = null; let y1 = null; let y2 = null; 17 | 18 | for (let i = 0; i < array.length; i++) { 19 | const inner = array[i]; 20 | 21 | for (let j = 0; j < inner.length; j++) { 22 | const lonlat = inner[j]; 23 | 24 | const lon = lonlat[0]; 25 | const lat = lonlat[1]; 26 | 27 | if (x1 === null) { 28 | x1 = lon; 29 | } else if (lon < x1) { 30 | x1 = lon; 31 | } 32 | 33 | if (x2 === null) { 34 | x2 = lon; 35 | } else if (lon > x2) { 36 | x2 = lon; 37 | } 38 | 39 | if (y1 === null) { 40 | y1 = lat; 41 | } else if (lat < y1) { 42 | y1 = lat; 43 | } 44 | 45 | if (y2 === null) { 46 | y2 = lat; 47 | } else if (lat > y2) { 48 | y2 = lat; 49 | } 50 | } 51 | } 52 | 53 | return [x1, y1, x2, y2]; 54 | }; 55 | 56 | /* 57 | Internal: Calculate a bounding box from an array of arrays of arrays 58 | [ 59 | [ [lng, lat],[lng, lat],[lng, lat] ] 60 | [ [lng, lat],[lng, lat],[lng, lat] ] 61 | [ [lng, lat],[lng, lat],[lng, lat] ] 62 | ] 63 | */ 64 | const calculateBoundsFromNestedArrayOfArrays = (array) => { 65 | let x1 = null; let x2 = null; let y1 = null; let y2 = null; 66 | 67 | for (let i = 0; i < array.length; i++) { 68 | const inner = array[i]; 69 | 70 | // return calculateBoundsFromNestedArrays(inner); // more DRY? 71 | for (let j = 0; j < inner.length; j++) { 72 | const innerinner = inner[j]; 73 | 74 | for (let k = 0; k < innerinner.length; k++) { 75 | const lonlat = innerinner[k]; 76 | 77 | const lon = lonlat[0]; 78 | const lat = lonlat[1]; 79 | 80 | if (x1 === null) { 81 | x1 = lon; 82 | } else if (lon < x1) { 83 | x1 = lon; 84 | } 85 | 86 | if (x2 === null) { 87 | x2 = lon; 88 | } else if (lon > x2) { 89 | x2 = lon; 90 | } 91 | 92 | if (y1 === null) { 93 | y1 = lat; 94 | } else if (lat < y1) { 95 | y1 = lat; 96 | } 97 | 98 | if (y2 === null) { 99 | y2 = lat; 100 | } else if (lat > y2) { 101 | y2 = lat; 102 | } 103 | } 104 | } 105 | } 106 | 107 | return [x1, y1, x2, y2]; 108 | }; 109 | 110 | /* 111 | Internal: Calculate a bounding box from an array of positions 112 | [ 113 | [lng, lat],[lng, lat],[lng, lat] 114 | ] 115 | */ 116 | const calculateBoundsFromArray = (array) => { 117 | let x1 = null; let x2 = null; let y1 = null; let y2 = null; 118 | 119 | for (let i = 0; i < array.length; i++) { 120 | const lonlat = array[i]; 121 | const lon = lonlat[0]; 122 | const lat = lonlat[1]; 123 | 124 | if (x1 === null) { 125 | x1 = lon; 126 | } else if (lon < x1) { 127 | x1 = lon; 128 | } 129 | 130 | if (x2 === null) { 131 | x2 = lon; 132 | } else if (lon > x2) { 133 | x2 = lon; 134 | } 135 | 136 | if (y1 === null) { 137 | y1 = lat; 138 | } else if (lat < y1) { 139 | y1 = lat; 140 | } 141 | 142 | if (y2 === null) { 143 | y2 = lat; 144 | } else if (lat > y2) { 145 | y2 = lat; 146 | } 147 | } 148 | 149 | return [x1, y1, x2, y2]; 150 | }; 151 | 152 | /* 153 | Internal: Calculate an bounding box for a feature collection 154 | */ 155 | const calculateBoundsForFeatureCollection = (featureCollection) => { 156 | const extents = []; 157 | for (let i = featureCollection.features.length - 1; i >= 0; i--) { 158 | const extent = calculateBounds(featureCollection.features[i].geometry); 159 | extents.push([extent[0], extent[1]]); 160 | extents.push([extent[2], extent[3]]); 161 | } 162 | 163 | return calculateBoundsFromArray(extents); 164 | }; 165 | 166 | /* 167 | Internal: Calculate an bounding box for a geometry collection 168 | */ 169 | const calculateBoundsForGeometryCollection = (geometryCollection) => { 170 | const extents = []; 171 | 172 | for (let i = geometryCollection.geometries.length - 1; i >= 0; i--) { 173 | const extent = calculateBounds(geometryCollection.geometries[i]); 174 | extents.push([extent[0], extent[1]]); 175 | extents.push([extent[2], extent[3]]); 176 | } 177 | 178 | return calculateBoundsFromArray(extents); 179 | }; 180 | 181 | export const calculateBounds = (geojson) => { 182 | if (geojson.type) { 183 | switch (geojson.type) { 184 | case 'Point': 185 | return [geojson.coordinates[0], geojson.coordinates[1], geojson.coordinates[0], geojson.coordinates[1]]; 186 | 187 | case 'MultiPoint': 188 | return calculateBoundsFromArray(geojson.coordinates); 189 | 190 | case 'LineString': 191 | return calculateBoundsFromArray(geojson.coordinates); 192 | 193 | case 'MultiLineString': 194 | return calculateBoundsFromNestedArrays(geojson.coordinates); 195 | 196 | case 'Polygon': 197 | return calculateBoundsFromNestedArrays(geojson.coordinates); 198 | 199 | case 'MultiPolygon': 200 | return calculateBoundsFromNestedArrayOfArrays(geojson.coordinates); 201 | 202 | case 'Feature': 203 | return geojson.geometry ? calculateBounds(geojson.geometry) : null; 204 | 205 | case 'FeatureCollection': 206 | return calculateBoundsForFeatureCollection(geojson); 207 | 208 | case 'GeometryCollection': 209 | return calculateBoundsForGeometryCollection(geojson); 210 | 211 | default: 212 | throw new Error('Unknown type: ' + geojson.type); 213 | } 214 | } 215 | return null; 216 | }; 217 | -------------------------------------------------------------------------------- /packages/spatial/src/circle.js: -------------------------------------------------------------------------------- 1 | import { applyConverter, closedPolygon } from './util'; 2 | 3 | import { 4 | positionToGeographic 5 | } from './position'; 6 | 7 | const VINCENTY = { 8 | a: 6378137, 9 | b: 6356752.3142, 10 | f: 1 / 298.257223563 11 | }; 12 | 13 | export const toGeographic = (geojson) => applyConverter(geojson, positionToGeographic); 14 | 15 | export const toCircle = (center, radius, interpolate) => { 16 | const steps = interpolate || 64; 17 | const rad = radius || 250; 18 | 19 | if (!center || center.length < 2 || !rad || !steps) { 20 | throw new Error('Terraformer: missing parameter for Terraformer.Circle'); 21 | } 22 | 23 | return { 24 | type: 'Feature', 25 | geometry: createGeodesicCircle(center, rad, steps), 26 | properties: { 27 | radius: rad, 28 | center: center, 29 | steps: steps 30 | } 31 | }; 32 | }; 33 | 34 | /* cribbed from 35 | http://stackoverflow.com/questions/24145205/writing-a-function-to-convert-a-circle-to-a-polygon-using-leaflet-js 36 | */ 37 | const createGeodesicCircle = (center, radius, interpolate) => { 38 | const steps = interpolate || 64; 39 | const polygon = { 40 | type: 'Polygon', 41 | coordinates: [[]] 42 | }; 43 | 44 | let angle; 45 | for (var i = 0; i < steps; i++) { 46 | angle = (i * 360 / steps); 47 | polygon.coordinates[0].push(destinationVincenty(center, angle, radius)); 48 | } 49 | 50 | polygon.coordinates = closedPolygon(polygon.coordinates); 51 | 52 | return polygon; 53 | }; 54 | 55 | const destinationVincenty = (coords, brng, dist) => { 56 | var cos2SigmaM, sinSigma, cosSigma, deltaSigma; 57 | var a = VINCENTY.a; var b = VINCENTY.b; var f = VINCENTY.f; 58 | var lon1 = coords[0]; 59 | var lat1 = coords[1]; 60 | var s = dist; 61 | var pi = Math.PI; 62 | var alpha1 = brng * pi / 180; // converts brng degrees to radius 63 | var sinAlpha1 = Math.sin(alpha1); 64 | var cosAlpha1 = Math.cos(alpha1); 65 | var tanU1 = (1 - f) * Math.tan(lat1 * pi / 180 /* converts lat1 degrees to radius */); 66 | var cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)); var sinU1 = tanU1 * cosU1; 67 | var sigma1 = Math.atan2(tanU1, cosAlpha1); 68 | var sinAlpha = cosU1 * sinAlpha1; 69 | var cosSqAlpha = 1 - sinAlpha * sinAlpha; 70 | var uSq = cosSqAlpha * (a * a - b * b) / (b * b); 71 | var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 72 | var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 73 | var sigma = s / (b * A); var sigmaP = 2 * Math.PI; 74 | while (Math.abs(sigma - sigmaP) > 1e-12) { 75 | cos2SigmaM = Math.cos(2 * sigma1 + sigma); 76 | sinSigma = Math.sin(sigma); 77 | cosSigma = Math.cos(sigma); 78 | deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - 79 | B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM))); 80 | sigmaP = sigma; 81 | sigma = s / (b * A) + deltaSigma; 82 | } 83 | var tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1; 84 | var lat2 = Math.atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, 85 | (1 - f) * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp)); 86 | var lambda = Math.atan2(sinSigma * sinAlpha1, cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1); 87 | var C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 88 | var lam = lambda - (1 - C) * f * sinAlpha * 89 | (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM))); 90 | var lamFunc = lon1 + (lam * 180 / pi); // converts lam radius to degrees 91 | var lat2a = lat2 * 180 / pi; // converts lat2a radius to degrees 92 | 93 | return [lamFunc, lat2a]; 94 | }; 95 | -------------------------------------------------------------------------------- /packages/spatial/src/constants.js: -------------------------------------------------------------------------------- 1 | export const EARTH_RADIUS = 6378137; 2 | export const DEGREES_PER_RADIAN = 57.295779513082320; 3 | export const RADIANS_PER_DEGREE = 0.017453292519943; 4 | 5 | export const MercatorCRS = { 6 | type: 'link', 7 | properties: { 8 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 9 | type: 'ogcwkt' 10 | } 11 | }; 12 | 13 | export const GeographicCRS = { 14 | type: 'link', 15 | properties: { 16 | href: 'http://spatialreference.org/ref/epsg/4326/ogcwkt/', 17 | type: 'ogcwkt' 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /packages/spatial/src/contains.js: -------------------------------------------------------------------------------- 1 | import { within } from './within'; 2 | 3 | export const contains = (geoJSON, comparisonGeoJSON) => within(comparisonGeoJSON, geoJSON); 4 | -------------------------------------------------------------------------------- /packages/spatial/src/convex.js: -------------------------------------------------------------------------------- 1 | import { 2 | closedPolygon, 3 | coordinateConvexHull 4 | } from './util'; 5 | 6 | export const convexHull = (geojson) => { 7 | let coordinates = []; let i; let j; 8 | if (geojson.type === 'Point') { 9 | return null; 10 | } else if (geojson.type === 'LineString' || geojson.type === 'MultiPoint') { 11 | if (geojson.coordinates && geojson.coordinates.length >= 3) { 12 | coordinates = geojson.coordinates; 13 | } else { 14 | return null; 15 | } 16 | } else if (geojson.type === 'Polygon' || geojson.type === 'MultiLineString') { 17 | if (geojson.coordinates && geojson.coordinates.length > 0) { 18 | for (i = 0; i < geojson.coordinates.length; i++) { 19 | coordinates = coordinates.concat(geojson.coordinates[i]); 20 | } 21 | if (coordinates.length < 3) { 22 | return null; 23 | } 24 | } else { 25 | return null; 26 | } 27 | } else if (geojson.type === 'MultiPolygon') { 28 | if (geojson.coordinates && geojson.coordinates.length > 0) { 29 | for (i = 0; i < geojson.coordinates.length; i++) { 30 | for (j = 0; j < geojson.coordinates[i].length; j++) { 31 | coordinates = coordinates.concat(geojson.coordinates[i][j]); 32 | } 33 | } 34 | if (coordinates.length < 3) { 35 | return null; 36 | } 37 | } else { 38 | return null; 39 | } 40 | } else if (geojson.type === 'Feature') { 41 | return convexHull(geojson.geometry); 42 | } 43 | 44 | return { 45 | type: 'Polygon', 46 | coordinates: closedPolygon([coordinateConvexHull(coordinates)]) 47 | }; 48 | }; 49 | 50 | export const isConvex = (points) => { 51 | let ltz; 52 | 53 | for (var i = 0; i < points.length - 3; i++) { 54 | const p1 = points[i]; 55 | const p2 = points[i + 1]; 56 | const p3 = points[i + 2]; 57 | const v = [p2[0] - p1[0], p2[1] - p1[1]]; 58 | 59 | // p3.x * v.y - p3.y * v.x + v.x * p1.y - v.y * p1.x 60 | const res = p3[0] * v[1] - p3[1] * v[0] + v[0] * p1[1] - v[1] * p1[0]; 61 | 62 | if (i === 0) { 63 | if (res < 0) { 64 | ltz = true; 65 | } else { 66 | ltz = false; 67 | } 68 | } else { 69 | if ((ltz && res > 0) || (!ltz && res < 0)) { 70 | return false; 71 | } 72 | } 73 | } 74 | 75 | return true; 76 | }; 77 | -------------------------------------------------------------------------------- /packages/spatial/src/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2019 Environmental Systems Research Institute, Inc. 2 | * Apache-2.0 */ 3 | 4 | /** @module Terraformer */ 5 | 6 | import { calculateBounds } from './bounds'; 7 | import { positionToMercator } from './position'; 8 | import { applyConverter } from './util'; 9 | 10 | export { 11 | /** 12 | * Runs the passed function against every Coordinate in the geojson object. 13 | * @function 14 | * @param {object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 15 | * @param {function} function - Your function will be passed a Coordinate and will be expected to return a Coordinate. 16 | * @return {object} GeoJSON - [GeoJSON](https://tools.ietf.org/html/rfc7946) with altered coordinates. 17 | * ```js 18 | * import { applyConverter } from "@terraformer/spatial" 19 | * 20 | * applyConverter({ 21 | * type: "Point", 22 | * coordinates: [ 45, 60 ] 23 | * }, (coord) => [coord[0] + 1, coord[1] - 1]) 24 | * 25 | * >> { type: "Point", coordinates: [ 46, 59 ] } 26 | * ``` 27 | */ 28 | applyConverter 29 | } from './util'; 30 | 31 | export { 32 | /** 33 | * Calculate the bounding box of the input. 34 | * @function 35 | * @param {object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 36 | * @return {Array.} [ xmin, ymin, xmax, ymax ]. 37 | * ```js 38 | * import { calculateBounds } from "@terraformer/spatial" 39 | * 40 | * calculateBounds({ 41 | * type: "Point", 42 | * coordinates: [ 45, 60 ] 43 | * }) 44 | * 45 | * >> [45, 60, 45, 60] 46 | * ``` 47 | */ 48 | calculateBounds 49 | } from './bounds'; 50 | 51 | export { 52 | /** 53 | * WKID [3857](https://epsg.io/3857) 54 | * @constant 55 | */ 56 | MercatorCRS 57 | } from './constants'; 58 | 59 | export { 60 | /** 61 | * WKID [4326](https://epsg.io/4326) 62 | * @constant 63 | */ 64 | GeographicCRS 65 | } from './constants'; 66 | 67 | /** 68 | * Calculate the envelope surrounding the input. 69 | * @function 70 | * @param {object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 71 | * @return {Object} Object in the form { x, y, w, h }. 72 | * ```js 73 | * import { calculateEnvelope } from "@terraformer/spatial" 74 | * 75 | * calculateEnvelope({ 76 | * type: "Point", 77 | * coordinates: [ 100, 100 ] 78 | * }) 79 | * 80 | * >> { x: 100, y: 100, w: 0, h: 0, } 81 | * ``` 82 | */ 83 | export const calculateEnvelope = (geojson) => { 84 | const bounds = calculateBounds(geojson); 85 | return { 86 | x: bounds[0], 87 | y: bounds[1], 88 | w: Math.abs(bounds[0] - bounds[2]), 89 | h: Math.abs(bounds[1] - bounds[3]) 90 | }; 91 | }; 92 | 93 | export { 94 | /** 95 | * Reprojects the passed Coordinate pair to WGS84 (4326) spatial reference. 96 | * @function 97 | * @param {Array.} CoordinatePair - An X,Y position. 98 | * @return {Array.} CoordinatePair. 99 | * ```js 100 | * import { positionToGeographic } from "@terraformer/spatial" 101 | * 102 | * positionToGeographic([ -13580978, 5621521 ]) // [ 45, 60 ] 103 | * ``` 104 | */ 105 | positionToGeographic 106 | } from './position'; 107 | 108 | export { 109 | /** 110 | * Reprojects the passed Coordinate pair to web mercator (3857) spatial reference. 111 | * @function 112 | * @param {Array.} CoordinatePair - An X,Y position. 113 | * @return {Array.} CoordinatePair. 114 | * ```js 115 | * import { positionToGeographic } from "@terraformer/spatial" 116 | * 117 | * positionToMercator([ 45, 60 ]) // [ -13580978, 5621521 ] 118 | * ``` 119 | */ 120 | positionToMercator 121 | } from './position'; 122 | 123 | /** 124 | * Reproject WGS84 (Lat/Lng) GeoJSON to Web Mercator. 125 | * @function 126 | * @param {object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 127 | * @return {object} GeoJSON 128 | * ```js 129 | * import { toMercator } from "@terraformer/spatial" 130 | * 131 | * toMercator({ 132 | * type: "Point", 133 | * coordinates: [ 45, 60 ] 134 | * }) 135 | * 136 | * >> { type: "Point", coordinates: [ -13580978, 5621521 ], crs } 137 | * ``` 138 | */ 139 | export const toMercator = (geojson) => applyConverter(geojson, positionToMercator); 140 | 141 | export { 142 | /** 143 | * Calculate the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of GeoJSON input. 144 | * @function 145 | * @param {object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 146 | * @return {Array.} An array of GeoJSON coordinates representing the convex hull of the input GeoJSON. 147 | * ```js 148 | * import { convexHull } from "@terraformer/spatial" 149 | * 150 | * convexHull({ 151 | * type: "LineString", 152 | * coordinates: [ 153 | * [ 100, 0 ], [ -45, 122 ], [ 80, -60 ] 154 | * ] 155 | * }) 156 | * 157 | * >> 158 | * { 159 | * type: "Polygon", 160 | * coordinates: [ 161 | * [ [ 100, 0 ], [ -45, 122 ], [ 80, -60 ], [ 100, 0 ] ] 162 | * ] 163 | * } 164 | * ``` 165 | */ 166 | convexHull 167 | } from './convex'; 168 | 169 | export { 170 | /** 171 | * Determine whether input GeoJSON has a [convex](https://en.wikipedia.org/wiki/Convex_set) shape. 172 | * @function 173 | * @param {Object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 174 | * @return {Boolean} Yes/No 175 | * ```js 176 | * import { isConvex } from "@terraformer/spatial" 177 | * 178 | * isConvex({ 179 | * type: "Polygon", 180 | * coordinates: [ 181 | * [ [ 100, 0 ], [ -45, 122 ], [ 80, -60 ], [ 100, 0 ] ] 182 | * ] 183 | * }) 184 | * 185 | * >> true 186 | * ``` 187 | */ 188 | isConvex 189 | } from './convex'; 190 | 191 | export { 192 | /** 193 | * Accepts the geometry of a polygon and point and returns `true` if the point falls within the polygon. 194 | * @function 195 | * @param {Object} GeoJSON - [GeoJSON Polygon](https://tools.ietf.org/html/rfc7946#section-3.1.6) coordinates. 196 | * @param {Object} GeoJSON - [GeoJSON Point](https://tools.ietf.org/html/rfc7946#section-3.1.2) coordinates. 197 | * @return {Boolean} Yes/No 198 | * ```js 199 | * import { polygonContainsPoint } from "@terraformer/spatial" 200 | * 201 | * polygonContainsPoint( 202 | * [ 203 | * [ [ 1, 2 ], [ 2, 2 ], [ 2, 1 ], [ 1, 1 ], [ 1, 2 ] ] 204 | * ], 205 | * [ 10, 10 ] 206 | * ) 207 | * 208 | * >> false 209 | * ``` 210 | */ 211 | polygonContainsPoint 212 | } from './polygon'; 213 | 214 | // to do: expose a close() method? 215 | 216 | export { 217 | /** 218 | * Returns `true` if the GeoJSON passed as the first argument is completely inside the GeoJSON object passed in the second position. 219 | * @function 220 | * @param {Object} GeoJSON - [GeoJSON](https://tools.ietf.org/html) that may be within the second input. 221 | * @param {Object} GeoJSON - [GeoJSON](https://tools.ietf.org/html/rfc7946#section-3.1.2) that may contain the first input. 222 | * @return {Boolean} Yes/No 223 | * ```js 224 | * import { within } from "@terraformer/spatial" 225 | * 226 | * within({ 227 | * type: "Point", 228 | * coordinates: [ 10, 10 ] 229 | * }, 230 | * { 231 | * type: "Polygon", 232 | * coordinates: [ 233 | * [ [ 5, 5 ], [ 5, 15 ], [ 15, 15 ], [ 15, 5 ], [ 5, 5 ] ] 234 | * ] 235 | * }) 236 | * 237 | * >> true 238 | * ``` 239 | */ 240 | within 241 | } from './within'; 242 | 243 | export { 244 | /** 245 | * Returns `true` if the GeoJSON passed as the second argument is completely inside the GeoJSON object passed in the first position. 246 | * @function 247 | * @param {Object} GeoJSON - [GeoJSON](https://tools.ietf.org/html) that may contain the second input. 248 | * @param {Object} GeoJSON - [GeoJSON](https://tools.ietf.org/html/rfc7946#section-3.1.2) that may be contained by the first input. 249 | * @return {Boolean} Yes/No 250 | * ```js 251 | * import { contains } from "@terraformer/spatial" 252 | * 253 | * contains({ 254 | * type: "Polygon", 255 | * coordinates: [ 256 | * [ [ 5, 5 ], [ 5, 15 ], [ 15, 15 ], [ 15, 5 ], [ 5, 5 ] ] 257 | * ]}, 258 | * { 259 | * type: "Point", 260 | * coordinates: [ 10, 10 ] 261 | * }) 262 | * 263 | * >> true 264 | * ``` 265 | */ 266 | contains 267 | } from './contains'; 268 | 269 | export { 270 | /** 271 | * Returns `true` if the two input GeoJSON objects intersect one another. 272 | * @function 273 | * @param {Object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 274 | * @param {Object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 275 | * @return {Boolean} Yes/No 276 | * ```js 277 | * import { intersects } from "@terraformer/spatial" 278 | * 279 | * intersects({ 280 | * type: "Point", 281 | * coordinates: [ 10, 10 ] 282 | * }, 283 | * { 284 | * type: "Polygon", 285 | * coordinates: [ 286 | * [ [ 5, 5 ], [ 5, 15 ], [ 15, 15 ], [ 15, 5 ], [ 5, 5 ] ] 287 | * ] 288 | * }) 289 | * 290 | * >> true 291 | * ``` 292 | */ 293 | intersects 294 | } from './intersects'; 295 | 296 | export { 297 | /** 298 | * Uses an input Coordinate pair to create a GeoJSON Feature containing a Polygon representing a circle with a discrete number of sides. 299 | * @function 300 | * @param {Array} CoordinatePair - A GeoJSON Coordinate in `[x,y]` format. 301 | * @param {Number} [Radius=250] - The radius of the circle (in meters). 302 | * @param {Number} [Steps=64] - The number of sides the output polygon will contain. 303 | * @return {object} GeoJSON 304 | * ```js 305 | * import { toCircle } from "@terraformer/spatial" 306 | * 307 | * toCircle([ -118, 34 ], 500) 308 | * 309 | * >> { type: "Feature", geometry: { type: "Polygon"}, coordinates: [...] } 310 | * ``` 311 | */ 312 | toCircle, 313 | /** 314 | * Reproject Web Mercator GeoJSON to WGS84 (Lat/Long). 315 | * @function 316 | * @param {object} GeoJSON - The input [GeoJSON](https://tools.ietf.org/html/rfc7946) Geometry, Feature, GeometryCollection or FeatureCollection. 317 | * @return {object} GeoJSON 318 | * ```js 319 | * import { toGeographic } from "@terraformer/spatial" 320 | * 321 | * toGeographic({ 322 | * type: "Point", 323 | * coordinates: [ -13580978, 5621521 ] 324 | * }) 325 | * 326 | * >> { type: "Point", coordinates: [ 45, 60 ] } 327 | * ``` 328 | */ 329 | toGeographic 330 | } from './circle'; 331 | -------------------------------------------------------------------------------- /packages/spatial/src/intersects.js: -------------------------------------------------------------------------------- 1 | import { 2 | arraysIntersectArrays 3 | } from '@terraformer/common'; 4 | 5 | import { warn } from './util'; 6 | import { within } from './within'; 7 | 8 | export const intersects = (geoJSON, comparisonGeoJSON) => { 9 | // if we are passed a feature, use the polygon inside instead 10 | if (comparisonGeoJSON.type === 'Feature') { 11 | comparisonGeoJSON = comparisonGeoJSON.geometry; 12 | } 13 | 14 | if (within(geoJSON, comparisonGeoJSON) || within(comparisonGeoJSON, geoJSON)) { 15 | return true; 16 | } 17 | 18 | if (geoJSON.type === 'MultiPolygon' && multipolygonIntersection(geoJSON, comparisonGeoJSON)) { 19 | return true; 20 | } 21 | 22 | if (geoJSON.type !== 'Point' && geoJSON.type !== 'MultiPoint' && 23 | comparisonGeoJSON.type !== 'Point' && comparisonGeoJSON.type !== 'MultiPoint') { 24 | return arraysIntersectArrays(geoJSON.coordinates, comparisonGeoJSON.coordinates); 25 | } else if (geoJSON.type === 'Feature') { 26 | // in the case of a Feature, use the internal geometry for intersection 27 | const inner = geoJSON.geometry; 28 | return intersects(inner, comparisonGeoJSON); 29 | } 30 | 31 | warn('Type ' + geoJSON.type + ' to ' + comparisonGeoJSON.type + ' intersection is not supported by intersects'); 32 | return false; 33 | }; 34 | 35 | function multipolygonIntersection (geoJSON, comparisonGeoJSON) { 36 | return geoJSON.coordinates.some(coordinates => { 37 | const componentPolygon = { 38 | type: 'Polygon', 39 | coordinates 40 | }; 41 | return within(componentPolygon, comparisonGeoJSON) || within(comparisonGeoJSON, componentPolygon); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /packages/spatial/src/polygon.js: -------------------------------------------------------------------------------- 1 | import { 2 | coordinatesContainPoint 3 | } from '@terraformer/common'; 4 | 5 | export const polygonContainsPoint = (polygon, point) => { 6 | if (polygon && polygon.length) { 7 | if (polygon.length === 1) { // polygon with no holes 8 | return coordinatesContainPoint(polygon[0], point); 9 | } else { // polygon with holes 10 | if (coordinatesContainPoint(polygon[0], point)) { 11 | for (let i = 1; i < polygon.length; i++) { 12 | if (coordinatesContainPoint(polygon[i], point)) { 13 | return false; // found in hole 14 | } 15 | } 16 | return true; 17 | } else { 18 | return false; 19 | } 20 | } 21 | } else { 22 | return false; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /packages/spatial/src/position.js: -------------------------------------------------------------------------------- 1 | import { 2 | EARTH_RADIUS, 3 | DEGREES_PER_RADIAN, 4 | RADIANS_PER_DEGREE 5 | } from './constants'; 6 | 7 | /* 8 | Internal: Convert radians to degrees. Used by spatial reference converters. 9 | */ 10 | export const radToDeg = (rad) => rad * DEGREES_PER_RADIAN; 11 | 12 | /* 13 | Internal: Convert degrees to radians. Used by spatial reference converters. 14 | */ 15 | export const degToRad = (deg) => deg * RADIANS_PER_DEGREE; 16 | 17 | export const positionToGeographic = (position) => { 18 | const x = position[0]; 19 | const y = position[1]; 20 | return [radToDeg(x / EARTH_RADIUS) - (Math.floor((radToDeg(x / EARTH_RADIUS) + 180) / 360) * 360), radToDeg((Math.PI / 2) - (2 * Math.atan(Math.exp(-1.0 * y / EARTH_RADIUS))))]; 21 | }; 22 | 23 | export const positionToMercator = (position) => { 24 | const lng = position[0]; 25 | const lat = Math.max(Math.min(position[1], 89.99999), -89.99999); 26 | return [degToRad(lng) * EARTH_RADIUS, EARTH_RADIUS / 2.0 * Math.log((1.0 + Math.sin(degToRad(lat))) / (1.0 - Math.sin(degToRad(lat))))]; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/spatial/src/util.js: -------------------------------------------------------------------------------- 1 | import { pointsEqual } from '@terraformer/common'; 2 | 3 | import { 4 | MercatorCRS 5 | } from './constants'; 6 | 7 | import { positionToMercator, positionToGeographic } from './position'; 8 | 9 | /* 10 | Internal: used for sorting 11 | */ 12 | export const compSort = (p1, p2) => { 13 | if (p1[0] > p2[0]) { 14 | return -1; 15 | } else if (p1[0] < p2[0]) { 16 | return 1; 17 | } else if (p1[1] > p2[1]) { 18 | return -1; 19 | } else if (p1[1] < p2[1]) { 20 | return 1; 21 | } else { 22 | return 0; 23 | } 24 | }; 25 | 26 | /* 27 | Internal: -1,0,1 comparison function 28 | */ 29 | const cmp = (a, b) => { 30 | if (a < b) { 31 | return -1; 32 | } else if (a > b) { 33 | return 1; 34 | } else { 35 | return 0; 36 | } 37 | }; 38 | 39 | /* 40 | Internal: used to determine turn 41 | */ 42 | const turn = (p, q, r) => { 43 | // Returns -1, 0, 1 if p,q,r forms a right, straight, or left turn. 44 | return cmp((q[0] - p[0]) * (r[1] - p[1]) - (r[0] - p[0]) * (q[1] - p[1]), 0); 45 | }; 46 | 47 | /* 48 | Internal: used to determine euclidean distance between two points 49 | */ 50 | const euclideanDistance = (p, q) => { 51 | // Returns the squared Euclidean distance between p and q. 52 | const dx = q[0] - p[0]; 53 | const dy = q[1] - p[1]; 54 | 55 | return dx * dx + dy * dy; 56 | }; 57 | 58 | const nextHullPoint = (points, p) => { 59 | // Returns the next point on the convex hull in CCW from p. 60 | let q = p; 61 | for (const r in points) { 62 | const t = turn(p, q, points[r]); 63 | if (t === -1 || (t === 0 && euclideanDistance(p, points[r]) > euclideanDistance(p, q))) { 64 | q = points[r]; 65 | } 66 | } 67 | return q; 68 | }; 69 | 70 | export const coordinateConvexHull = (points) => { 71 | // implementation of the Jarvis March algorithm 72 | // adapted from http://tixxit.wordpress.com/2009/12/09/jarvis-march/ 73 | 74 | if (points.length === 0) { 75 | return []; 76 | } else if (points.length === 1) { 77 | return points; 78 | } 79 | 80 | // Returns the points on the convex hull of points in CCW order. 81 | const hull = [points.sort(compSort)[0]]; 82 | 83 | for (var p = 0; p < hull.length; p++) { 84 | const q = nextHullPoint(points, hull[p]); 85 | 86 | if (q !== hull[0]) { 87 | hull.push(q); 88 | } 89 | } 90 | 91 | return hull; 92 | }; 93 | 94 | /* 95 | Internal: Returns a copy of coordinates for a closed polygon 96 | */ 97 | export const closedPolygon = (coordinates) => { 98 | const outer = []; 99 | 100 | for (let i = 0; i < coordinates.length; i++) { 101 | const inner = coordinates[i].slice(); 102 | if (pointsEqual(inner[0], inner[inner.length - 1]) === false) { 103 | inner.push(inner[0]); 104 | } 105 | 106 | outer.push(inner); 107 | } 108 | 109 | return outer; 110 | }; 111 | 112 | /* 113 | Internal: safe warning 114 | */ 115 | export function warn () { 116 | const args = Array.prototype.slice.apply(arguments); 117 | 118 | if (typeof console !== 'undefined' && console.warn) { 119 | console.warn.apply(console, args); 120 | } 121 | } 122 | 123 | export const hasHoles = (geojson) => geojson.coordinates.length > 1; 124 | 125 | /* 126 | Internal: Loop over each array in a geojson object and apply a function to it. Used by spatial reference converters. 127 | */ 128 | const eachPosition = (coordinates, func) => { 129 | for (let i = 0; i < coordinates.length; i++) { 130 | // we found a number so lets convert the pair 131 | if (typeof coordinates[i][0] === 'number') { 132 | coordinates[i] = func(coordinates[i]); 133 | } 134 | // we found an coordinates array it again and run the function against it 135 | if (typeof coordinates[i] === 'object') { 136 | coordinates[i] = eachPosition(coordinates[i], func); 137 | } 138 | } 139 | return coordinates; 140 | }; 141 | 142 | /* 143 | Apply a function agaist all positions in a geojson object. Used by spatial reference converters. 144 | */ 145 | export const applyConverter = (geojson, converter, noCrs) => { 146 | if (geojson.type === 'Point') { 147 | geojson.coordinates = converter(geojson.coordinates); 148 | } else if (geojson.type === 'Feature') { 149 | geojson.geometry = applyConverter(geojson.geometry, converter, true); 150 | } else if (geojson.type === 'FeatureCollection') { 151 | for (let f = 0; f < geojson.features.length; f++) { 152 | geojson.features[f] = applyConverter(geojson.features[f], converter, true); 153 | } 154 | } else if (geojson.type === 'GeometryCollection') { 155 | for (let g = 0; g < geojson.geometries.length; g++) { 156 | geojson.geometries[g] = applyConverter(geojson.geometries[g], converter, true); 157 | } 158 | } else { 159 | geojson.coordinates = eachPosition(geojson.coordinates, converter); 160 | } 161 | 162 | if (!noCrs) { 163 | if (converter === positionToMercator) { 164 | geojson.crs = MercatorCRS; 165 | } 166 | } 167 | 168 | if (converter === positionToGeographic) { 169 | delete geojson.crs; 170 | } 171 | 172 | return geojson; 173 | }; 174 | 175 | export const coordinatesEqual = (a, b) => { 176 | if (a.length !== b.length) { 177 | return false; 178 | } 179 | 180 | const na = a.slice().sort(compSort); 181 | const nb = b.slice().sort(compSort); 182 | 183 | for (let i = 0; i < na.length; i++) { 184 | if (na[i].length !== nb[i].length) { 185 | return false; 186 | } 187 | for (let j = 0; j < na.length; j++) { 188 | if (na[i][j] !== nb[i][j]) { 189 | return false; 190 | } 191 | } 192 | } 193 | 194 | return true; 195 | }; 196 | -------------------------------------------------------------------------------- /packages/spatial/src/within.js: -------------------------------------------------------------------------------- 1 | import { 2 | arraysIntersectArrays, 3 | pointsEqual 4 | } from '@terraformer/common'; 5 | 6 | import { closedPolygon, coordinatesEqual } from './util'; 7 | 8 | import { polygonContainsPoint } from './polygon'; 9 | 10 | export const within = (geoJSON, comparisonGeoJSON) => { 11 | let coordinates, i, contains; 12 | 13 | // if we are passed a feature, use the polygon inside instead 14 | if (comparisonGeoJSON.type === 'Feature') { 15 | comparisonGeoJSON = comparisonGeoJSON.geometry; 16 | } 17 | 18 | // point.within(point) :: equality 19 | if (comparisonGeoJSON.type === 'Point') { 20 | if (geoJSON.type === 'Point') { 21 | return pointsEqual(geoJSON.coordinates, comparisonGeoJSON.coordinates); 22 | } 23 | } 24 | 25 | // point.within(multilinestring) 26 | if (comparisonGeoJSON.type === 'MultiLineString') { 27 | if (geoJSON.type === 'Point') { 28 | for (i = 0; i < geoJSON.coordinates.length; i++) { 29 | const linestring = { type: 'LineString', coordinates: comparisonGeoJSON.coordinates[i] }; 30 | 31 | if (within(geoJSON, linestring)) { 32 | return true; 33 | } 34 | } 35 | } 36 | } 37 | 38 | // point.within(linestring), point.within(multipoint) 39 | if (comparisonGeoJSON.type === 'LineString' || comparisonGeoJSON.type === 'MultiPoint') { 40 | if (geoJSON.type === 'Point') { 41 | for (i = 0; i < comparisonGeoJSON.coordinates.length; i++) { 42 | if (geoJSON.coordinates.length !== comparisonGeoJSON.coordinates[i].length) { 43 | return false; 44 | } 45 | 46 | if (pointsEqual(geoJSON.coordinates, comparisonGeoJSON.coordinates[i])) { 47 | return true; 48 | } 49 | } 50 | } 51 | } 52 | 53 | if (comparisonGeoJSON.type === 'Polygon') { 54 | // polygon.within(polygon) 55 | if (geoJSON.type === 'Polygon') { 56 | // check for equal polygons 57 | if (comparisonGeoJSON.coordinates.length === geoJSON.coordinates.length) { 58 | for (i = 0; i < geoJSON.coordinates.length; i++) { 59 | if (coordinatesEqual(geoJSON.coordinates[i], comparisonGeoJSON.coordinates[i])) { 60 | return true; 61 | } 62 | } 63 | } 64 | 65 | if (geoJSON.coordinates.length && polygonContainsPoint(comparisonGeoJSON.coordinates, geoJSON.coordinates[0][0])) { 66 | return !arraysIntersectArrays(closedPolygon(geoJSON.coordinates), closedPolygon(comparisonGeoJSON.coordinates)); 67 | } else { 68 | return false; 69 | } 70 | 71 | // point.within(polygon) 72 | } else if (geoJSON.type === 'Point') { 73 | return polygonContainsPoint(comparisonGeoJSON.coordinates, geoJSON.coordinates); 74 | 75 | // linestring/multipoint withing polygon 76 | } else if (geoJSON.type === 'LineString' || geoJSON.type === 'MultiPoint') { 77 | if (!geoJSON.coordinates || geoJSON.coordinates.length === 0) { 78 | return false; 79 | } 80 | 81 | for (i = 0; i < geoJSON.coordinates.length; i++) { 82 | if (polygonContainsPoint(comparisonGeoJSON.coordinates, geoJSON.coordinates[i]) === false) { 83 | return false; 84 | } 85 | } 86 | 87 | return true; 88 | 89 | // multilinestring.within(polygon) 90 | } else if (geoJSON.type === 'MultiLineString') { 91 | for (i = 0; i < geoJSON.coordinates.length; i++) { 92 | const ls = { 93 | type: 'LineString', 94 | coordinates: geoJSON.coordinates[i] 95 | }; 96 | if (within(ls, comparisonGeoJSON) === false) { 97 | contains++; 98 | return false; 99 | } 100 | } 101 | 102 | return true; 103 | 104 | // multipolygon.within(polygon) 105 | } else if (geoJSON.type === 'MultiPolygon') { 106 | for (i = 0; i < geoJSON.coordinates.length; i++) { 107 | const p1 = { type: 'Polygon', coordinates: geoJSON.coordinates[i] }; 108 | 109 | if (within(p1, comparisonGeoJSON) === false) { 110 | return false; 111 | } 112 | } 113 | 114 | return true; 115 | } 116 | } 117 | 118 | if (comparisonGeoJSON.type === 'MultiPolygon') { 119 | // point.within(multipolygon) 120 | if (geoJSON.type === 'Point') { 121 | if (comparisonGeoJSON.coordinates.length) { 122 | for (i = 0; i < comparisonGeoJSON.coordinates.length; i++) { 123 | coordinates = comparisonGeoJSON.coordinates[i]; 124 | if (polygonContainsPoint(coordinates, geoJSON.coordinates) && arraysIntersectArrays([geoJSON.coordinates], comparisonGeoJSON.coordinates) === false) { 125 | return true; 126 | } 127 | } 128 | } 129 | 130 | return false; 131 | // polygon.within(multipolygon) 132 | } else if (geoJSON.type === 'Polygon') { 133 | for (i = 0; i < geoJSON.coordinates.length; i++) { 134 | if (comparisonGeoJSON.coordinates[i].length === geoJSON.coordinates.length) { 135 | for (let j = 0; j < geoJSON.coordinates.length; j++) { 136 | if (coordinatesEqual(geoJSON.coordinates[j], comparisonGeoJSON.coordinates[i][j])) { 137 | return true; 138 | } 139 | } 140 | } 141 | } 142 | 143 | if (arraysIntersectArrays(geoJSON.coordinates, comparisonGeoJSON.coordinates) === false) { 144 | if (comparisonGeoJSON.coordinates.length) { 145 | for (i = 0; i < comparisonGeoJSON.coordinates.length; i++) { 146 | coordinates = comparisonGeoJSON.coordinates[i]; 147 | if (polygonContainsPoint(coordinates, geoJSON.coordinates[0][0]) === false) { 148 | contains = false; 149 | } else { 150 | contains = true; 151 | } 152 | } 153 | 154 | return contains; 155 | } 156 | } 157 | 158 | // linestring.within(multipolygon), multipoint.within(multipolygon) 159 | } else if (geoJSON.type === 'LineString' || geoJSON.type === 'MultiPoint') { 160 | for (i = 0; i < comparisonGeoJSON.coordinates.length; i++) { 161 | const poly = { type: 'Polygon', coordinates: comparisonGeoJSON.coordinates[i] }; 162 | 163 | if (within(geoJSON, poly)) { 164 | return true; 165 | } 166 | 167 | return false; 168 | } 169 | 170 | // multilinestring.within(multipolygon) 171 | } else if (geoJSON.type === 'MultiLineString') { 172 | for (i = 0; i < geoJSON.coordinates.length; i++) { 173 | const ls = { type: 'LineString', coordinates: geoJSON.coordinates[i] }; 174 | 175 | if (within(ls, comparisonGeoJSON) === false) { 176 | return false; 177 | } 178 | } 179 | 180 | return true; 181 | 182 | // multipolygon.within(multipolygon) 183 | } else if (geoJSON.type === 'MultiPolygon') { 184 | for (i = 0; i < comparisonGeoJSON.coordinates.length; i++) { 185 | const mpoly = { type: 'Polygon', coordinates: comparisonGeoJSON.coordinates[i] }; 186 | 187 | if (within(geoJSON, mpoly) === false) { 188 | return false; 189 | } 190 | } 191 | 192 | return true; 193 | } 194 | } 195 | 196 | // default to false 197 | return false; 198 | }; 199 | -------------------------------------------------------------------------------- /packages/spatial/test/crs.test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | 3 | import { 4 | toMercator, 5 | toGeographic 6 | } from '../src/index'; 7 | 8 | test('should exist', function (t) { 9 | t.plan(2); 10 | t.ok(toMercator); 11 | t.ok(toGeographic); 12 | }); 13 | 14 | test('should project a GeoJSON Point in WGS84 to Web Mercator', function (t) { 15 | t.plan(1); 16 | const input = { 17 | type: 'Point', 18 | coordinates: [-122, 45] 19 | }; 20 | const expectedOutput = { 21 | type: 'Point', 22 | coordinates: [-13580977.876779145, 5621521.486191948], 23 | crs: { 24 | type: 'link', 25 | properties: { 26 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 27 | type: 'ogcwkt' 28 | } 29 | } 30 | }; 31 | toMercator(input); 32 | t.deepEqual(input, expectedOutput); 33 | }); 34 | 35 | test('should project a GeoJSON LineString to Web Mercator.', function (t) { 36 | t.plan(1); 37 | const input = { 38 | type: 'MultiPoint', 39 | coordinates: [[-122, 45], [100, 0], [45, 62]] 40 | }; 41 | var expectedOutput = { 42 | type: 'MultiPoint', 43 | coordinates: [[-13580977.876779145, 5621521.486191948], [11131949.079327168, 0], [5009377.085697226, 8859142.800565446]], 44 | crs: { 45 | type: 'link', 46 | properties: { 47 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 48 | type: 'ogcwkt' 49 | } 50 | } 51 | }; 52 | toMercator(input); 53 | t.deepEqual(input, expectedOutput); 54 | }); 55 | 56 | test('should project a GeoJSON LineString to Web Mercator.', function (t) { 57 | t.plan(1); 58 | const input = { 59 | type: 'LineString', 60 | coordinates: [[-122, 45], [100, 0], [45, 62]] 61 | }; 62 | var expectedOutput = { 63 | type: 'LineString', 64 | coordinates: [[-13580977.876779145, 5621521.486191948], [11131949.079327168, 0], [5009377.085697226, 8859142.800565446]], 65 | crs: { 66 | type: 'link', 67 | properties: { 68 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 69 | type: 'ogcwkt' 70 | } 71 | } 72 | }; 73 | toMercator(input); 74 | t.deepEqual(input, expectedOutput); 75 | }); 76 | 77 | test('should project a GeoJSON MultiLineString to Web Mercator.', function (t) { 78 | t.plan(1); 79 | const input = { 80 | type: 'MultiLineString', 81 | coordinates: [ 82 | [[41.8359375, 71.015625], [56.953125, 33.75]], 83 | [[21.796875, 36.5625], [47.8359375, 71.015625]] 84 | ] 85 | }; 86 | var expectedOutput = { 87 | type: 'MultiLineString', 88 | coordinates: [ 89 | [[4657155.25935914, 11407616.835043576], [6339992.874085551, 3995282.329624162]], 90 | [[2426417.025884594, 4378299.115616046], [5325072.20411877, 11407616.835043576]] 91 | ], 92 | crs: { 93 | type: 'link', 94 | properties: { 95 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 96 | type: 'ogcwkt' 97 | } 98 | } 99 | }; 100 | toMercator(input); 101 | t.deepEqual(input, expectedOutput); 102 | }); 103 | 104 | test('should project a GeoJSON Polygon to Web Mercator.', function (t) { 105 | t.plan(1); 106 | const input = { 107 | type: 'Polygon', 108 | coordinates: [ 109 | [[41.8359375, 71.015625], [56.953125, 33.75], [21.796875, 36.5625], [41.8359375, 71.015625]] 110 | ] 111 | }; 112 | var expectedOutput = { 113 | type: 'Polygon', 114 | coordinates: [[[4657155.25935914, 11407616.835043576], [6339992.874085551, 3995282.329624162], [2426417.025884594, 4378299.115616046], [4657155.25935914, 11407616.835043576]]], 115 | crs: { 116 | type: 'link', 117 | properties: { 118 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 119 | type: 'ogcwkt' 120 | } 121 | } 122 | }; 123 | toMercator(input); 124 | t.deepEqual(input, expectedOutput); 125 | }); 126 | 127 | test('should project a GeoJSON MultiPolygon to Web Mercator.', function (t) { 128 | t.plan(1); 129 | const input = { 130 | type: 'MultiPolygon', 131 | coordinates: [ 132 | [ 133 | [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]] 134 | ], 135 | [ 136 | [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] 137 | ] 138 | ] 139 | }; 140 | 141 | var expectedOutput = { 142 | type: 'MultiPolygon', 143 | coordinates: [[[[11354588.060913712, 222684.2085055407], [11465907.551706985, 222684.2085055407], [11465907.551706985, 334111.1714019535], [11354588.060913712, 334111.1714019535], [11354588.060913712, 222684.2085055407]]], [[[11131949.079327168, 0], [11243268.57012044, 0], [11243268.57012044, 111325.14286638329], [11131949.079327168, 111325.14286638329], [11131949.079327168, 0]]]], 144 | crs: { 145 | type: 'link', 146 | properties: { 147 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 148 | type: 'ogcwkt' 149 | } 150 | } 151 | }; 152 | toMercator(input); 153 | t.deepEqual(input, expectedOutput); 154 | }); 155 | 156 | test('should project a GeoJSON Feature to Web Mercator.', function (t) { 157 | t.plan(1); 158 | const input = { 159 | type: 'Feature', 160 | id: 'foo', 161 | geometry: { 162 | type: 'Point', 163 | coordinates: [-122, 45] 164 | }, 165 | properties: { 166 | bar: 'baz' 167 | } 168 | }; 169 | var expectedOutput = { 170 | type: 'Feature', 171 | id: 'foo', 172 | geometry: { 173 | type: 'Point', 174 | coordinates: [-13580977.876779145, 5621521.486191948] 175 | }, 176 | properties: { 177 | bar: 'baz' 178 | }, 179 | crs: { 180 | type: 'link', 181 | properties: { 182 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 183 | type: 'ogcwkt' 184 | } 185 | } 186 | }; 187 | toMercator(input); 188 | t.deepEqual(input, expectedOutput); 189 | }); 190 | 191 | test('should project a GeoJSON FeatureCollection to Web Mercator.', function (t) { 192 | t.plan(1); 193 | const input = { 194 | type: 'FeatureCollection', 195 | features: [ 196 | { 197 | type: 'Feature', 198 | id: 'foo', 199 | geometry: { 200 | type: 'Point', 201 | coordinates: [-122, 45] 202 | }, 203 | properties: { 204 | bar: 'baz' 205 | } 206 | }, { 207 | type: 'Feature', 208 | id: 'bar', 209 | geometry: { 210 | type: 'Point', 211 | coordinates: [-122, 45] 212 | }, 213 | properties: { 214 | bar: 'baz' 215 | } 216 | } 217 | ] 218 | }; 219 | var expectedOutput = { 220 | type: 'FeatureCollection', 221 | features: [ 222 | { 223 | type: 'Feature', 224 | id: 'foo', 225 | geometry: { 226 | type: 'Point', 227 | coordinates: [-13580977.876779145, 5621521.486191948] 228 | }, 229 | properties: { 230 | bar: 'baz' 231 | } 232 | }, { 233 | type: 'Feature', 234 | id: 'bar', 235 | geometry: { 236 | type: 'Point', 237 | coordinates: [-13580977.876779145, 5621521.486191948] 238 | }, 239 | properties: { 240 | bar: 'baz' 241 | } 242 | } 243 | ], 244 | crs: { 245 | type: 'link', 246 | properties: { 247 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 248 | type: 'ogcwkt' 249 | } 250 | } 251 | }; 252 | toMercator(input); 253 | t.deepEqual(input, expectedOutput); 254 | }); 255 | 256 | // 257 | test('should project a GeoJSON GeometryCollection to Web Mercator.', function (t) { 258 | t.plan(1); 259 | const input = { 260 | type: 'GeometryCollection', 261 | geometries: [ 262 | { 263 | type: 'Point', 264 | coordinates: [-122, 45] 265 | }, { 266 | type: 'Point', 267 | coordinates: [-122, 45] 268 | } 269 | ] 270 | }; 271 | var expectedOutput = { 272 | type: 'GeometryCollection', 273 | geometries: [ 274 | { 275 | type: 'Point', 276 | coordinates: [-13580977.876779145, 5621521.486191948] 277 | }, { 278 | type: 'Point', 279 | coordinates: [-13580977.876779145, 5621521.486191948] 280 | } 281 | ], 282 | crs: { 283 | type: 'link', 284 | properties: { 285 | href: 'http://spatialreference.org/ref/sr-org/6928/ogcwkt/', 286 | type: 'ogcwkt' 287 | } 288 | } 289 | }; 290 | toMercator(input); 291 | t.deepEqual(input, expectedOutput); 292 | }); 293 | 294 | test('should project a GeoJSON Point to WGS84.', function (t) { 295 | t.plan(1); 296 | const input = { 297 | type: 'Point', 298 | coordinates: [-13656274.38035172, 5703203.67194997] 299 | }; 300 | var expectedOutput = { 301 | type: 'Point', 302 | coordinates: [-122.67639999999798, 45.516499999999255] 303 | }; 304 | toGeographic(input); 305 | t.deepEqual(input, expectedOutput); 306 | }); 307 | 308 | test('should project a GeoJSON MultiPoint to WGS84.', function (t) { 309 | t.plan(1); 310 | const input = { 311 | type: 'MultiPoint', 312 | coordinates: [[-13656274.380351715, 5703203.671949966], [11131949.079327168, 0], [-13619241.057432571, 6261718.09354067]] 313 | }; 314 | var expectedOutput = { 315 | type: 'MultiPoint', 316 | coordinates: [[-122.67639999999793, 45.51649999999923], [99.99999999999831, 0], [-122.34372399999793, 48.92247999999917]] 317 | }; 318 | toGeographic(input); 319 | t.deepEqual(input, expectedOutput); 320 | }); 321 | 322 | test('should project a GeoJSON LineString to WGS84.', function (t) { 323 | t.plan(1); 324 | const input = { 325 | type: 'LineString', 326 | coordinates: [[743579.411158182, 6075718.008992066], [-7279251.077653782, 6869641.046935855], [-5831228.013819427, 5242073.5675988225]] 327 | }; 328 | var expectedOutput = { 329 | type: 'LineString', 330 | coordinates: [[6.679687499999886, 47.8124999999992], [-65.3906249999989, 52.38281249999911], [-52.38281249999912, 42.53906249999928]] 331 | }; 332 | toGeographic(input); 333 | t.deepEqual(input, expectedOutput); 334 | }); 335 | 336 | test('should project a GeoJSON MultiLineString to WGS84.', function (t) { 337 | t.plan(1); 338 | const input = { 339 | type: 'MultiLineString', 340 | coordinates: [ 341 | [[41.8359375, 71.015625], [56.953125, 33.75]], 342 | [[21.796875, 36.5625], [47.8359375, 71.015625]] 343 | ] 344 | }; 345 | var expectedOutput = { 346 | type: 'MultiLineString', 347 | coordinates: [ 348 | [[0.00037581862081719045, 0.0006379442134689308], [0.0005116186266586962, 0.00030318140838354136]], [[0.00019580465958542694, 0.00032844652575519755], [0.0004297175378643617, 0.0006379442134689308]] 349 | ] 350 | }; 351 | toGeographic(input); 352 | t.deepEqual(input, expectedOutput); 353 | }); 354 | 355 | test('should project a GeoJSON Polygon to WGS84.', function (t) { 356 | t.plan(1); 357 | const input = { 358 | type: 'Polygon', 359 | coordinates: [ 360 | [[41.8359375, 71.015625], [56.953125, 33.75], [21.796875, 36.5625], [41.8359375, 71.015625]] 361 | ] 362 | }; 363 | var expectedOutput = { 364 | type: 'Polygon', 365 | coordinates: [ 366 | [[0.00037581862081719045, 0.0006379442134689308], [0.0005116186266586962, 0.00030318140838354136], [0.00019580465958542694, 0.00032844652575519755], [0.00037581862081719045, 0.0006379442134689308]] 367 | ] 368 | }; 369 | toGeographic(input); 370 | t.deepEqual(input, expectedOutput); 371 | }); 372 | 373 | test('should project a GeoJSON MultiPolygon to WGS84.', function (t) { 374 | t.plan(1); 375 | const input = { 376 | type: 'MultiPolygon', 377 | coordinates: [ 378 | [ 379 | [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]] 380 | ], 381 | [ 382 | [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] 383 | ] 384 | ] 385 | }; 386 | 387 | var expectedOutput = { 388 | type: 'MultiPolygon', 389 | coordinates: [ 390 | [ 391 | [[0.000916281589801912, 0.000017966305681987637], [0.0009252647426431071, 0.000017966305681987637], [0.0009252647426431071, 0.000026949458522981454], [0.000916281589801912, 0.000026949458522981454], [0.000916281589801912, 0.000017966305681987637]] 392 | ], 393 | [ 394 | [[0.0008983152841195215, 0], [0.0009072984369607167, 0], [0.0009072984369607167, 0.000008983152840993819], [0.0008983152841195215, 0.000008983152840993819], [0.0008983152841195215, 0]] 395 | ] 396 | ] 397 | }; 398 | toGeographic(input); 399 | t.deepEqual(input, expectedOutput); 400 | }); 401 | 402 | test('should project a GeoJSON Feature to WGS84.', function (t) { 403 | t.plan(1); 404 | const input = { 405 | type: 'Feature', 406 | id: 'foo', 407 | geometry: { 408 | type: 'Point', 409 | coordinates: [-13656274.380351715, 5703203.671949966] 410 | }, 411 | properties: { 412 | bar: 'baz' 413 | } 414 | }; 415 | var expectedOutput = { 416 | type: 'Feature', 417 | id: 'foo', 418 | geometry: { 419 | type: 'Point', 420 | coordinates: [-122.67639999999793, 45.51649999999923] 421 | }, 422 | properties: { 423 | bar: 'baz' 424 | } 425 | }; 426 | toGeographic(input); 427 | t.deepEqual(input, expectedOutput); 428 | }); 429 | 430 | test('should project a GeoJSON FeatureCollection to WGS84.', function (t) { 431 | t.plan(1); 432 | const input = { 433 | type: 'FeatureCollection', 434 | features: [ 435 | { 436 | type: 'Feature', 437 | id: 'foo', 438 | geometry: { 439 | type: 'Point', 440 | coordinates: [-13656274.380351715, 5703203.671949966] 441 | }, 442 | properties: { 443 | bar: 'baz' 444 | } 445 | }, { 446 | type: 'Feature', 447 | id: 'bar', 448 | geometry: { 449 | type: 'Point', 450 | coordinates: [-13656274.380351715, 5703203.671949966] 451 | }, 452 | properties: { 453 | bar: 'baz' 454 | } 455 | } 456 | ] 457 | }; 458 | var expectedOutput = { 459 | type: 'FeatureCollection', 460 | features: [ 461 | { 462 | type: 'Feature', 463 | id: 'foo', 464 | geometry: { 465 | type: 'Point', 466 | coordinates: [-122.67639999999793, 45.51649999999923] 467 | }, 468 | properties: { 469 | bar: 'baz' 470 | } 471 | }, { 472 | type: 'Feature', 473 | id: 'bar', 474 | geometry: { 475 | type: 'Point', 476 | coordinates: [-122.67639999999793, 45.51649999999923] 477 | }, 478 | properties: { 479 | bar: 'baz' 480 | } 481 | } 482 | ] 483 | }; 484 | toGeographic(input); 485 | t.deepEqual(input, expectedOutput); 486 | }); 487 | 488 | test('should project a GeoJSON GeometryCollection to WGS84.', function (t) { 489 | t.plan(1); 490 | const input = { 491 | type: 'GeometryCollection', 492 | geometries: [ 493 | { 494 | type: 'Point', 495 | coordinates: [-13656274.380351715, 5703203.671949966] 496 | }, { 497 | type: 'Point', 498 | coordinates: [-13656274.380351715, 5703203.671949966] 499 | } 500 | ] 501 | }; 502 | var expectedOutput = { 503 | type: 'GeometryCollection', 504 | geometries: [ 505 | { 506 | type: 'Point', 507 | coordinates: [-122.67639999999793, 45.51649999999923] 508 | }, { 509 | type: 'Point', 510 | coordinates: [-122.67639999999793, 45.51649999999923] 511 | } 512 | ] 513 | }; 514 | toGeographic(input); 515 | t.deepEqual(input, expectedOutput); 516 | }); 517 | -------------------------------------------------------------------------------- /packages/wkt/README.hbs: -------------------------------------------------------------------------------- 1 | # @terraformer/wkt 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | [![standard][standard-image]][standard-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/@terraformer/wkt.svg?style=flat-square 8 | [npm-url]: https://www.npmjs.com/package/@terraformer/wkt 9 | [travis-image]: https://app.travis-ci.com/terraformer-js/terraformer.svg?branch=main 10 | [travis-url]: https://app.travis-ci.com/terraformer-js/terraformer 11 | [standard-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square 12 | [standard-url]: http://npm.im/semistandard 13 | 14 | > Tools to convert WKT geometries to GeoJSON geometries and vice versa. 15 | 16 | ## Install 17 | 18 | ```shell 19 | npm install @terraformer/wkt 20 | ``` 21 | 22 | ## API Reference 23 | 24 | {{>main}} 25 | * * * 26 | 27 | ## Usage 28 | 29 | ### Browser (from CDN) 30 | 31 | This package is distributed as a [UMD](https://github.com/umdjs/umd) module and can also be used in AMD based systems or as a global under the `Terraformer` namespace. 32 | 33 | ```html 34 | 35 | ``` 36 | ```js 37 | Terraformer.wktToGeoJSON("POINT (-122.6764 45.5165)"); 38 | ``` 39 | 40 | ### Node.js 41 | 42 | ```js 43 | const Terraformer = require('@terraformer/wkt'); 44 | 45 | Terraformer.geojsonToWKT(/* ... */); 46 | Terraformer.wktToGeoJSON(/* ... */); 47 | ``` 48 | 49 | ### ES module in the browser 50 | 51 | ```html 52 | 58 | ``` 59 | 60 | ## [Contributing](./CONTRIBUTING.md) 61 | 62 | ## [LICENSE](https://raw.githubusercontent.com/terraformer-js/terraformer/master/LICENSE) 63 | -------------------------------------------------------------------------------- /packages/wkt/README.md: -------------------------------------------------------------------------------- 1 | # @terraformer/wkt 2 | 3 | [![npm][npm-image]][npm-url] 4 | [![travis][travis-image]][travis-url] 5 | [![standard][standard-image]][standard-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/@terraformer/wkt.svg?style=flat-square 8 | [npm-url]: https://www.npmjs.com/package/@terraformer/wkt 9 | [travis-image]: https://app.travis-ci.com/terraformer-js/terraformer.svg?branch=main 10 | [travis-url]: https://app.travis-ci.com/terraformer-js/terraformer 11 | [standard-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square 12 | [standard-url]: http://npm.im/semistandard 13 | 14 | > Tools to convert WKT geometries to GeoJSON geometries and vice versa. 15 | 16 | ## Install 17 | 18 | ```shell 19 | npm install @terraformer/wkt 20 | ``` 21 | 22 | ## API Reference 23 | 24 | 25 | 26 | ## Terraformer 27 | 28 | * [Terraformer](#module_Terraformer) 29 | * [.wktToGeoJSON(WKT)](#module_Terraformer.wktToGeoJSON) ⇒ object 30 | * [.geojsonToWKT(GeoJSON)](#module_Terraformer.geojsonToWKT) ⇒ string 31 | 32 | 33 | 34 | ### Terraformer.wktToGeoJSON(WKT) ⇒ object 35 | Converts a [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) geometry into a GeoJSON geometry. 36 | 37 | **Kind**: static method of [Terraformer](#module_Terraformer) 38 | **Returns**: object - GeoJSON. 39 | 40 | ```js 41 | import { wktToGeoJSON } from "@terraformer/wkt" 42 | 43 | wktToGeoJSON("POINT (-122.6764 45.5165)"); 44 | 45 | >> { "type": "Point", "coordinates": [ -122.6764, 45.5165 ] } 46 | ``` 47 | 48 | | Param | Type | Description | 49 | | --- | --- | --- | 50 | | WKT | string | The input WKT geometry. | 51 | 52 | 53 | 54 | ### Terraformer.geojsonToWKT(GeoJSON) ⇒ string 55 | Converts a GeoJSON geometry or GeometryCollection into a [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) string. 56 | 57 | **Kind**: static method of [Terraformer](#module_Terraformer) 58 | **Returns**: string - WKT. 59 | ```js 60 | import { geojsonToWKT } from "@terraformer/wkt" 61 | 62 | const geojsonPoint = { 63 | "type": "Point", 64 | "coordinates": [-122.6764, 45.5165] 65 | } 66 | 67 | geojsonToWKT(geojsonPoint) 68 | 69 | >> "POINT (-122.6764 45.5165)" 70 | ``` 71 | 72 | | Param | Type | Description | 73 | | --- | --- | --- | 74 | | GeoJSON | object | The input GeoJSON geometry or GeometryCollection. | 75 | 76 | * * * 77 | 78 | ## Usage 79 | 80 | ### Browser (from CDN) 81 | 82 | This package is distributed as a [UMD](https://github.com/umdjs/umd) module and can also be used in AMD based systems or as a global under the `Terraformer` namespace. 83 | 84 | ```html 85 | 86 | ``` 87 | ```js 88 | Terraformer.wktToGeoJSON("POINT (-122.6764 45.5165)"); 89 | ``` 90 | 91 | ### Node.js 92 | 93 | ```js 94 | const Terraformer = require('@terraformer/wkt'); 95 | 96 | Terraformer.geojsonToWKT(/* ... */); 97 | Terraformer.wktToGeoJSON(/* ... */); 98 | ``` 99 | 100 | ### ES module in the browser 101 | 102 | ```html 103 | 109 | ``` 110 | 111 | ## [Contributing](./CONTRIBUTING.md) 112 | 113 | ## [LICENSE](https://raw.githubusercontent.com/terraformer-js/terraformer/master/LICENSE) 114 | -------------------------------------------------------------------------------- /packages/wkt/build-esm.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var jison = require('jison'); 3 | 4 | var grammar = fs.readFileSync('./src/wkt.yy', 'utf8'); 5 | var wrapper = fs.readFileSync('./src/wkt.js', 'utf8'); 6 | 7 | var Parser = jison.Parser; 8 | var parser = new Parser(grammar); 9 | 10 | // generate source, ready to be written to disk using a jison fork to get a es module output: https://github.com/zaach/jison/pull/326 11 | var parserSource = parser.generate({ moduleType: 'es' }); 12 | 13 | wrapper = wrapper.replace('\'SOURCE\';', parserSource); 14 | fs.writeFileSync('./src/index.js', wrapper, 'utf8'); 15 | -------------------------------------------------------------------------------- /packages/wkt/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terraformer/wkt", 3 | "version": "2.2.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@terraformer/wkt", 9 | "version": "2.2.1", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "jison": "https://github.com/GabrielRatener/jison" 13 | } 14 | }, 15 | "node_modules/amdefine": { 16 | "version": "1.0.1", 17 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 18 | "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", 19 | "dev": true, 20 | "optional": true, 21 | "engines": { 22 | "node": ">=0.4.2" 23 | } 24 | }, 25 | "node_modules/cjson": { 26 | "version": "0.3.0", 27 | "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.3.0.tgz", 28 | "integrity": "sha512-bBRQcCIHzI1IVH59fR0bwGrFmi3Btb/JNwM/n401i1DnYgWndpsUBiQRAddLflkZage20A2d25OAWZZk0vBRlA==", 29 | "dev": true, 30 | "dependencies": { 31 | "jsonlint": "1.6.0" 32 | }, 33 | "engines": { 34 | "node": ">= 0.3.0" 35 | } 36 | }, 37 | "node_modules/colors": { 38 | "version": "0.5.1", 39 | "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", 40 | "integrity": "sha512-XjsuUwpDeY98+yz959OlUK6m7mLBM+1MEG5oaenfuQnNnrQk1WvtcvFgN3FNDP3f2NmZ211t0mNEfSEN1h0eIg==", 41 | "dev": true, 42 | "engines": { 43 | "node": ">=0.1.90" 44 | } 45 | }, 46 | "node_modules/ebnf-parser": { 47 | "version": "0.1.10", 48 | "resolved": "https://registry.npmjs.org/ebnf-parser/-/ebnf-parser-0.1.10.tgz", 49 | "integrity": "sha512-urvSxVQ6XJcoTpc+/x2pWhhuOX4aljCNQpwzw+ifZvV1andZkAmiJc3Rq1oGEAQmcjiLceyMXOy1l8ms8qs2fQ==", 50 | "dev": true 51 | }, 52 | "node_modules/escodegen": { 53 | "version": "1.3.3", 54 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", 55 | "integrity": "sha512-z9FWgKc48wjMlpzF5ymKS1AF8OIgnKLp9VyN7KbdtyrP/9lndwUFqCtMm+TAJmJf7KJFFYc4cFJfVTTGkKEwsA==", 56 | "dev": true, 57 | "dependencies": { 58 | "esprima": "~1.1.1", 59 | "estraverse": "~1.5.0", 60 | "esutils": "~1.0.0" 61 | }, 62 | "bin": { 63 | "escodegen": "bin/escodegen.js", 64 | "esgenerate": "bin/esgenerate.js" 65 | }, 66 | "engines": { 67 | "node": ">=0.10.0" 68 | }, 69 | "optionalDependencies": { 70 | "source-map": "~0.1.33" 71 | } 72 | }, 73 | "node_modules/esprima": { 74 | "version": "1.1.1", 75 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", 76 | "integrity": "sha512-qxxB994/7NtERxgXdFgLHIs9M6bhLXc6qtUmWZ3L8+gTQ9qaoyki2887P2IqAYsoENyr8SUbTutStDniOHSDHg==", 77 | "dev": true, 78 | "bin": { 79 | "esparse": "bin/esparse.js", 80 | "esvalidate": "bin/esvalidate.js" 81 | }, 82 | "engines": { 83 | "node": ">=0.4.0" 84 | } 85 | }, 86 | "node_modules/estraverse": { 87 | "version": "1.5.1", 88 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", 89 | "integrity": "sha512-FpCjJDfmo3vsc/1zKSeqR5k42tcIhxFIlvq+h9j0fO2q/h2uLKyweq7rYJ+0CoVvrGQOxIS5wyBrW/+vF58BUQ==", 90 | "dev": true, 91 | "engines": { 92 | "node": ">=0.4.0" 93 | } 94 | }, 95 | "node_modules/esutils": { 96 | "version": "1.0.0", 97 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", 98 | "integrity": "sha512-x/iYH53X3quDwfHRz4y8rn4XcEwwCJeWsul9pF1zldMbGtgOtMNBEOuYWwB1EQlK2LRa1fev3YAgym/RElp5Cg==", 99 | "dev": true, 100 | "engines": { 101 | "node": ">=0.10.0" 102 | } 103 | }, 104 | "node_modules/jison": { 105 | "version": "0.4.17", 106 | "resolved": "git+ssh://git@github.com/GabrielRatener/jison.git#2668d7efbb628c763dc7cce41ab24a4632528942", 107 | "integrity": "sha512-NHThbqidF4gPr4YwTd6MUFqGiUdWbYqcBAFH1gPYWaLmoHWafH/kCGjDmJkFKEV2XkeWAyODEBT95mzZVa67sA==", 108 | "dev": true, 109 | "license": "MIT", 110 | "dependencies": { 111 | "cjson": "0.3.0", 112 | "ebnf-parser": "0.1.10", 113 | "escodegen": "1.3.x", 114 | "esprima": "1.1.x", 115 | "jison-lex": "0.3.x", 116 | "JSONSelect": "0.4.0", 117 | "lex-parser": "~0.1.3", 118 | "nomnom": "1.5.2" 119 | }, 120 | "bin": { 121 | "jison": "lib/cli.js" 122 | }, 123 | "engines": { 124 | "node": ">=0.4" 125 | } 126 | }, 127 | "node_modules/jison-lex": { 128 | "version": "0.3.4", 129 | "resolved": "https://registry.npmjs.org/jison-lex/-/jison-lex-0.3.4.tgz", 130 | "integrity": "sha512-EBh5wrXhls1cUwROd5DcDHR1sG7CdsCFSqY1027+YA1RGxz+BX2TDLAhdsQf40YEtFDGoiO0Qm8PpnBl2EzDJw==", 131 | "dev": true, 132 | "dependencies": { 133 | "lex-parser": "0.1.x", 134 | "nomnom": "1.5.2" 135 | }, 136 | "bin": { 137 | "jison-lex": "cli.js" 138 | }, 139 | "engines": { 140 | "node": ">=0.4" 141 | } 142 | }, 143 | "node_modules/jsonlint": { 144 | "version": "1.6.0", 145 | "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.0.tgz", 146 | "integrity": "sha512-x6YLBe6NjdpmIeiklwQOxsZuYj/SOWkT33GlTpaG1UdFGjdWjPcxJ1CWZAX3wA7tarz8E2YHF6KiW5HTapPlXw==", 147 | "dev": true, 148 | "dependencies": { 149 | "JSV": ">= 4.0.x", 150 | "nomnom": ">= 1.5.x" 151 | }, 152 | "bin": { 153 | "jsonlint": "lib/cli.js" 154 | }, 155 | "engines": { 156 | "node": ">= 0.6" 157 | } 158 | }, 159 | "node_modules/JSONSelect": { 160 | "version": "0.4.0", 161 | "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz", 162 | "integrity": "sha512-VRLR3Su35MH+XV2lrvh9O7qWoug/TUyj9tLDjn9rtpUCNnILLrHjgd/tB0KrhugCxUpj3UqoLqfYb3fLJdIQQQ==", 163 | "dev": true, 164 | "engines": { 165 | "node": ">=0.4.7" 166 | } 167 | }, 168 | "node_modules/JSV": { 169 | "version": "4.0.2", 170 | "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", 171 | "integrity": "sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==", 172 | "dev": true, 173 | "engines": { 174 | "node": "*" 175 | } 176 | }, 177 | "node_modules/lex-parser": { 178 | "version": "0.1.4", 179 | "resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz", 180 | "integrity": "sha512-DuAEISsr1H4LOpmFLkyMc8YStiRWZCO8hMsoXAXSbgyfvs2WQhSt0+/FBv3ZU/JBFZMGcE+FWzEBSzwUU7U27w==", 181 | "dev": true 182 | }, 183 | "node_modules/nomnom": { 184 | "version": "1.5.2", 185 | "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", 186 | "integrity": "sha512-fiVbT7BqxiQqjlR9U3FDGOSERFCKoXVCdxV2FwZuNN7/cmJ42iQx35nUFOAFDcyvemu9Adp+IlsCGlKQYLmBKw==", 187 | "deprecated": "Package no longer supported. Contact support@npmjs.com for more info.", 188 | "dev": true, 189 | "dependencies": { 190 | "colors": "0.5.x", 191 | "underscore": "1.1.x" 192 | }, 193 | "engines": { 194 | "node": "*" 195 | } 196 | }, 197 | "node_modules/source-map": { 198 | "version": "0.1.43", 199 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 200 | "integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==", 201 | "dev": true, 202 | "optional": true, 203 | "dependencies": { 204 | "amdefine": ">=0.0.4" 205 | }, 206 | "engines": { 207 | "node": ">=0.8.0" 208 | } 209 | }, 210 | "node_modules/underscore": { 211 | "version": "1.1.7", 212 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", 213 | "integrity": "sha512-w4QtCHoLBXw1mjofIDoMyexaEdWGMedWNDhlWTtT1V1lCRqi65Pnoygkh6+WRdr+Bm8ldkBNkNeCsXGMlQS9HQ==", 214 | "dev": true, 215 | "engines": { 216 | "node": "*" 217 | } 218 | } 219 | }, 220 | "dependencies": { 221 | "amdefine": { 222 | "version": "1.0.1", 223 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 224 | "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", 225 | "dev": true, 226 | "optional": true 227 | }, 228 | "cjson": { 229 | "version": "0.3.0", 230 | "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.3.0.tgz", 231 | "integrity": "sha512-bBRQcCIHzI1IVH59fR0bwGrFmi3Btb/JNwM/n401i1DnYgWndpsUBiQRAddLflkZage20A2d25OAWZZk0vBRlA==", 232 | "dev": true, 233 | "requires": { 234 | "jsonlint": "1.6.0" 235 | } 236 | }, 237 | "colors": { 238 | "version": "0.5.1", 239 | "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", 240 | "integrity": "sha512-XjsuUwpDeY98+yz959OlUK6m7mLBM+1MEG5oaenfuQnNnrQk1WvtcvFgN3FNDP3f2NmZ211t0mNEfSEN1h0eIg==", 241 | "dev": true 242 | }, 243 | "ebnf-parser": { 244 | "version": "0.1.10", 245 | "resolved": "https://registry.npmjs.org/ebnf-parser/-/ebnf-parser-0.1.10.tgz", 246 | "integrity": "sha512-urvSxVQ6XJcoTpc+/x2pWhhuOX4aljCNQpwzw+ifZvV1andZkAmiJc3Rq1oGEAQmcjiLceyMXOy1l8ms8qs2fQ==", 247 | "dev": true 248 | }, 249 | "escodegen": { 250 | "version": "1.3.3", 251 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", 252 | "integrity": "sha512-z9FWgKc48wjMlpzF5ymKS1AF8OIgnKLp9VyN7KbdtyrP/9lndwUFqCtMm+TAJmJf7KJFFYc4cFJfVTTGkKEwsA==", 253 | "dev": true, 254 | "requires": { 255 | "esprima": "~1.1.1", 256 | "estraverse": "~1.5.0", 257 | "esutils": "~1.0.0", 258 | "source-map": "~0.1.33" 259 | } 260 | }, 261 | "esprima": { 262 | "version": "1.1.1", 263 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", 264 | "integrity": "sha512-qxxB994/7NtERxgXdFgLHIs9M6bhLXc6qtUmWZ3L8+gTQ9qaoyki2887P2IqAYsoENyr8SUbTutStDniOHSDHg==", 265 | "dev": true 266 | }, 267 | "estraverse": { 268 | "version": "1.5.1", 269 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", 270 | "integrity": "sha512-FpCjJDfmo3vsc/1zKSeqR5k42tcIhxFIlvq+h9j0fO2q/h2uLKyweq7rYJ+0CoVvrGQOxIS5wyBrW/+vF58BUQ==", 271 | "dev": true 272 | }, 273 | "esutils": { 274 | "version": "1.0.0", 275 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", 276 | "integrity": "sha512-x/iYH53X3quDwfHRz4y8rn4XcEwwCJeWsul9pF1zldMbGtgOtMNBEOuYWwB1EQlK2LRa1fev3YAgym/RElp5Cg==", 277 | "dev": true 278 | }, 279 | "jison": { 280 | "version": "git+ssh://git@github.com/GabrielRatener/jison.git#2668d7efbb628c763dc7cce41ab24a4632528942", 281 | "integrity": "sha512-NHThbqidF4gPr4YwTd6MUFqGiUdWbYqcBAFH1gPYWaLmoHWafH/kCGjDmJkFKEV2XkeWAyODEBT95mzZVa67sA==", 282 | "dev": true, 283 | "from": "jison@https://github.com/GabrielRatener/jison", 284 | "requires": { 285 | "cjson": "0.3.0", 286 | "ebnf-parser": "0.1.10", 287 | "escodegen": "1.3.x", 288 | "esprima": "1.1.x", 289 | "jison-lex": "0.3.x", 290 | "JSONSelect": "0.4.0", 291 | "lex-parser": "~0.1.3", 292 | "nomnom": "1.5.2" 293 | } 294 | }, 295 | "jison-lex": { 296 | "version": "0.3.4", 297 | "resolved": "https://registry.npmjs.org/jison-lex/-/jison-lex-0.3.4.tgz", 298 | "integrity": "sha512-EBh5wrXhls1cUwROd5DcDHR1sG7CdsCFSqY1027+YA1RGxz+BX2TDLAhdsQf40YEtFDGoiO0Qm8PpnBl2EzDJw==", 299 | "dev": true, 300 | "requires": { 301 | "lex-parser": "0.1.x", 302 | "nomnom": "1.5.2" 303 | } 304 | }, 305 | "jsonlint": { 306 | "version": "1.6.0", 307 | "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.0.tgz", 308 | "integrity": "sha512-x6YLBe6NjdpmIeiklwQOxsZuYj/SOWkT33GlTpaG1UdFGjdWjPcxJ1CWZAX3wA7tarz8E2YHF6KiW5HTapPlXw==", 309 | "dev": true, 310 | "requires": { 311 | "JSV": ">= 4.0.x", 312 | "nomnom": ">= 1.5.x" 313 | } 314 | }, 315 | "JSONSelect": { 316 | "version": "0.4.0", 317 | "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz", 318 | "integrity": "sha512-VRLR3Su35MH+XV2lrvh9O7qWoug/TUyj9tLDjn9rtpUCNnILLrHjgd/tB0KrhugCxUpj3UqoLqfYb3fLJdIQQQ==", 319 | "dev": true 320 | }, 321 | "JSV": { 322 | "version": "4.0.2", 323 | "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", 324 | "integrity": "sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==", 325 | "dev": true 326 | }, 327 | "lex-parser": { 328 | "version": "0.1.4", 329 | "resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz", 330 | "integrity": "sha512-DuAEISsr1H4LOpmFLkyMc8YStiRWZCO8hMsoXAXSbgyfvs2WQhSt0+/FBv3ZU/JBFZMGcE+FWzEBSzwUU7U27w==", 331 | "dev": true 332 | }, 333 | "nomnom": { 334 | "version": "1.5.2", 335 | "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", 336 | "integrity": "sha512-fiVbT7BqxiQqjlR9U3FDGOSERFCKoXVCdxV2FwZuNN7/cmJ42iQx35nUFOAFDcyvemu9Adp+IlsCGlKQYLmBKw==", 337 | "dev": true, 338 | "requires": { 339 | "colors": "0.5.x", 340 | "underscore": "1.1.x" 341 | } 342 | }, 343 | "source-map": { 344 | "version": "0.1.43", 345 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 346 | "integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==", 347 | "dev": true, 348 | "optional": true, 349 | "requires": { 350 | "amdefine": ">=0.0.4" 351 | } 352 | }, 353 | "underscore": { 354 | "version": "1.1.7", 355 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", 356 | "integrity": "sha512-w4QtCHoLBXw1mjofIDoMyexaEdWGMedWNDhlWTtT1V1lCRqi65Pnoygkh6+WRdr+Bm8ldkBNkNeCsXGMlQS9HQ==", 357 | "dev": true 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /packages/wkt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terraformer/wkt", 3 | "description": "Tools to convert WKT geometries to GeoJSON geometries and vica-versa.", 4 | "version": "2.2.1", 5 | "author": "Jerry Sievert (http://legitimatesounding.com)", 6 | "bugs": { 7 | "url": "https://github.com/terraformer-js/terraformer/issues" 8 | }, 9 | "contributors": [ 10 | "John Gravois " 11 | ], 12 | "devDependencies": { 13 | "jison": "https://github.com/GabrielRatener/jison" 14 | }, 15 | "files": [ 16 | "dist/**" 17 | ], 18 | "homepage": "https://github.com/terraformer-js/terraformer", 19 | "keywords": [ 20 | "wkt", 21 | "convert", 22 | "geo", 23 | "geojson", 24 | "geometry" 25 | ], 26 | "license": "MIT", 27 | "main": "dist/t-wkt.umd.js", 28 | "unpkg": "dist/t-wkt.umd.js", 29 | "jsdelivr": "dist/t-wkt.umd.js", 30 | "module": "dist/t-wkt.esm.js", 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/terraformer-js/terraformer" 34 | }, 35 | "scripts": { 36 | "build:module": "node build-esm.js", 37 | "prebuild": "npm run build:module", 38 | "build": "rollup -c ../../rollup.umd.config.js && rollup -c ../../rollup.esm.config.js", 39 | "doc": "jsdoc2md --files src/index.js --template README.hbs > README.md" 40 | }, 41 | "publishConfig": { 42 | "access": "public" 43 | }, 44 | "gitHead": "8fb60ae0c5a13fb7f31fc29d028d889b22091bfa" 45 | } 46 | -------------------------------------------------------------------------------- /packages/wkt/src/wkt.js: -------------------------------------------------------------------------------- 1 | /* global parser */ // via jison 2 | 3 | /* Copyright (c) 2012-2020 Environmental Systems Research Institute, Inc. 4 | * MIT */ 5 | 6 | /** @module Terraformer */ 7 | 8 | 'SOURCE'; 9 | 10 | // surface parsing errors to calling code https://github.com/zaach/jison/issues/218 11 | parser.yy.parseError = function (err) { 12 | throw err; 13 | }; 14 | 15 | function PointArray (point) { 16 | this.data = [point]; 17 | this.type = 'PointArray'; 18 | } 19 | 20 | PointArray.prototype.addPoint = function (point) { 21 | if (point.type === 'PointArray') { 22 | this.data = this.data.concat(point.data); 23 | } else { 24 | this.data.push(point); 25 | } 26 | 27 | return this; 28 | }; 29 | 30 | PointArray.prototype.toJSON = function () { 31 | return this.data; 32 | }; 33 | 34 | function Ring (point) { 35 | this.data = point; 36 | this.type = 'Ring'; 37 | } 38 | 39 | Ring.prototype.toJSON = function () { 40 | var data = []; 41 | 42 | for (var i = 0; i < this.data.data.length; i++) { 43 | data.push(this.data.data[i]); 44 | } 45 | 46 | return data; 47 | }; 48 | 49 | function RingList (ring) { 50 | this.data = [ring]; 51 | this.type = 'RingList'; 52 | } 53 | 54 | RingList.prototype.addRing = function (ring) { 55 | this.data.push(ring); 56 | 57 | return this; 58 | }; 59 | 60 | RingList.prototype.toJSON = function () { 61 | var data = []; 62 | 63 | for (var i = 0; i < this.data.length; i++) { 64 | data.push(this.data[i].toJSON()); 65 | } 66 | 67 | if (data.length === 1) { 68 | return data; 69 | } else { 70 | return data; 71 | } 72 | }; 73 | 74 | function GeometryList (geometry) { 75 | this.data = [geometry]; 76 | this.type = 'GeometryList'; 77 | } 78 | 79 | GeometryList.prototype.addGeometry = function (geometry) { 80 | this.data.push(geometry); 81 | 82 | return this; 83 | }; 84 | 85 | GeometryList.prototype.toJSON = function () { 86 | return this.data; 87 | }; 88 | 89 | function PolygonList (polygon) { 90 | this.data = [polygon]; 91 | this.type = 'PolygonList'; 92 | } 93 | 94 | PolygonList.prototype.addPolygon = function (polygon) { 95 | this.data.push(polygon); 96 | 97 | return this; 98 | }; 99 | 100 | PolygonList.prototype.toJSON = function () { 101 | var data = []; 102 | 103 | for (var i = 0; i < this.data.length; i++) { 104 | data = data.concat([this.data[i].toJSON()]); 105 | } 106 | 107 | return data; 108 | }; 109 | 110 | /** 111 | * Converts a [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) geometry into a GeoJSON geometry. 112 | * @function 113 | * @param {string} WKT - The input WKT geometry. 114 | * @return {object} GeoJSON. 115 | * 116 | * ```js 117 | * import { wktToGeoJSON } from "@terraformer/wkt" 118 | * 119 | * wktToGeoJSON("POINT (-122.6764 45.5165)"); 120 | * 121 | * >> { "type": "Point", "coordinates": [ -122.6764, 45.5165 ] } 122 | * ``` 123 | */ 124 | export const wktToGeoJSON = (element) => { 125 | let res; 126 | 127 | try { 128 | res = parser.parse(element); 129 | } catch (err) { 130 | throw Error('Unable to parse: ' + err); 131 | } 132 | 133 | return res; 134 | }; 135 | 136 | const arrayToRing = (arr) => { 137 | const parts = []; 138 | let ret = ''; 139 | 140 | for (var i = 0; i < arr.length; i++) { 141 | parts.push(arr[i].join(' ')); 142 | } 143 | 144 | ret += '(' + parts.join(', ') + ')'; 145 | 146 | return ret; 147 | }; 148 | 149 | const pointToWKTPoint = (geojson) => { 150 | let ret = 'POINT '; 151 | 152 | if (geojson.coordinates === undefined || geojson.coordinates.length === 0) { 153 | ret += 'EMPTY'; 154 | 155 | return ret; 156 | } else if (geojson.coordinates.length === 3) { 157 | // 3d or time? default to 3d 158 | if (geojson.properties && geojson.properties.m === true) { 159 | ret += 'M '; 160 | } else { 161 | ret += 'Z '; 162 | } 163 | } else if (geojson.coordinates.length === 4) { 164 | // 3d and time 165 | ret += 'ZM '; 166 | } 167 | 168 | // include coordinates 169 | ret += '(' + geojson.coordinates.join(' ') + ')'; 170 | 171 | return ret; 172 | }; 173 | 174 | const lineStringToWKTLineString = (geojson) => { 175 | let ret = 'LINESTRING '; 176 | 177 | if (geojson.coordinates === undefined || geojson.coordinates.length === 0 || geojson.coordinates[0].length === 0) { 178 | ret += 'EMPTY'; 179 | 180 | return ret; 181 | } else if (geojson.coordinates[0].length === 3) { 182 | if (geojson.properties && geojson.properties.m === true) { 183 | ret += 'M '; 184 | } else { 185 | ret += 'Z '; 186 | } 187 | } else if (geojson.coordinates[0].length === 4) { 188 | ret += 'ZM '; 189 | } 190 | 191 | ret += arrayToRing(geojson.coordinates); 192 | 193 | return ret; 194 | }; 195 | 196 | const polygonToWKTPolygon = (geojson) => { 197 | let ret = 'POLYGON '; 198 | 199 | if (geojson.coordinates === undefined || geojson.coordinates.length === 0 || geojson.coordinates[0].length === 0) { 200 | ret += 'EMPTY'; 201 | 202 | return ret; 203 | } else if (geojson.coordinates[0][0].length === 3) { 204 | if (geojson.properties && geojson.properties.m === true) { 205 | ret += 'M '; 206 | } else { 207 | ret += 'Z '; 208 | } 209 | } else if (geojson.coordinates[0][0].length === 4) { 210 | ret += 'ZM '; 211 | } 212 | 213 | ret += '('; 214 | var parts = []; 215 | for (var i = 0; i < geojson.coordinates.length; i++) { 216 | parts.push(arrayToRing(geojson.coordinates[i])); 217 | } 218 | 219 | ret += parts.join(', '); 220 | ret += ')'; 221 | 222 | return ret; 223 | }; 224 | 225 | const multiPointToWKTMultiPoint = (geojson) => { 226 | var ret = 'MULTIPOINT '; 227 | 228 | if (geojson.coordinates === undefined || geojson.coordinates.length === 0 || geojson.coordinates[0].length === 0) { 229 | ret += 'EMPTY'; 230 | 231 | return ret; 232 | } else if (geojson.coordinates[0].length === 3) { 233 | if (geojson.properties && geojson.properties.m === true) { 234 | ret += 'M '; 235 | } else { 236 | ret += 'Z '; 237 | } 238 | } else if (geojson.coordinates[0].length === 4) { 239 | ret += 'ZM '; 240 | } 241 | 242 | ret += arrayToRing(geojson.coordinates); 243 | 244 | return ret; 245 | }; 246 | 247 | const multiLineStringToWKTMultiLineString = (geojson) => { 248 | let ret = 'MULTILINESTRING '; 249 | 250 | if (geojson.coordinates === undefined || geojson.coordinates.length === 0 || geojson.coordinates[0].length === 0) { 251 | ret += 'EMPTY'; 252 | 253 | return ret; 254 | } else if (geojson.coordinates[0][0].length === 3) { 255 | if (geojson.properties && geojson.properties.m === true) { 256 | ret += 'M '; 257 | } else { 258 | ret += 'Z '; 259 | } 260 | } else if (geojson.coordinates[0][0].length === 4) { 261 | ret += 'ZM '; 262 | } 263 | 264 | ret += '('; 265 | const parts = []; 266 | for (var i = 0; i < geojson.coordinates.length; i++) { 267 | parts.push(arrayToRing(geojson.coordinates[i])); 268 | } 269 | 270 | ret += parts.join(', '); 271 | ret += ')'; 272 | 273 | return ret; 274 | }; 275 | 276 | const multiPolygonToWKTMultiPolygon = (geojson) => { 277 | var ret = 'MULTIPOLYGON '; 278 | 279 | if (geojson.coordinates === undefined || geojson.coordinates.length === 0 || geojson.coordinates[0].length === 0) { 280 | ret += 'EMPTY'; 281 | 282 | return ret; 283 | } else if (geojson.coordinates[0][0][0].length === 3) { 284 | if (geojson.properties && geojson.properties.m === true) { 285 | ret += 'M '; 286 | } else { 287 | ret += 'Z '; 288 | } 289 | } else if (geojson.coordinates[0][0][0].length === 4) { 290 | ret += 'ZM '; 291 | } 292 | 293 | ret += '('; 294 | var inner = []; 295 | for (var c = 0; c < geojson.coordinates.length; c++) { 296 | var it = '('; 297 | var parts = []; 298 | for (var i = 0; i < geojson.coordinates[c].length; i++) { 299 | parts.push(arrayToRing(geojson.coordinates[c][i])); 300 | } 301 | 302 | it += parts.join(', '); 303 | it += ')'; 304 | 305 | inner.push(it); 306 | } 307 | 308 | ret += inner.join(', '); 309 | ret += ')'; 310 | 311 | return ret; 312 | }; 313 | 314 | /** 315 | * Converts a GeoJSON geometry or GeometryCollection into a [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) string. 316 | * @function 317 | * @param {object} GeoJSON - The input GeoJSON geometry or GeometryCollection. 318 | * @return {string} WKT. 319 | * ```js 320 | * import { geojsonToWKT } from "@terraformer/wkt" 321 | * 322 | * const geojsonPoint = { 323 | * "type": "Point", 324 | * "coordinates": [-122.6764, 45.5165] 325 | * } 326 | * 327 | * geojsonToWKT(geojsonPoint) 328 | * 329 | * >> "POINT (-122.6764 45.5165)" 330 | * ``` 331 | */ 332 | export const geojsonToWKT = (geojson) => { 333 | switch (geojson.type) { 334 | case 'Point': 335 | return pointToWKTPoint(geojson); 336 | case 'LineString': 337 | return lineStringToWKTLineString(geojson); 338 | case 'Polygon': 339 | return polygonToWKTPolygon(geojson); 340 | case 'MultiPoint': 341 | return multiPointToWKTMultiPoint(geojson); 342 | case 'MultiLineString': 343 | return multiLineStringToWKTMultiLineString(geojson); 344 | case 'MultiPolygon': 345 | return multiPolygonToWKTMultiPolygon(geojson); 346 | case 'GeometryCollection': 347 | var ret = 'GEOMETRYCOLLECTION'; 348 | var parts = []; 349 | for (let i = 0; i < geojson.geometries.length; i++) { 350 | parts.push(geojsonToWKT(geojson.geometries[i])); 351 | } 352 | return ret + '(' + parts.join(', ') + ')'; 353 | default: 354 | throw Error('Unknown Type: ' + geojson.type); 355 | } 356 | }; 357 | -------------------------------------------------------------------------------- /packages/wkt/src/wkt.yy: -------------------------------------------------------------------------------- 1 | 2 | 3 | %lex 4 | 5 | %% 6 | 7 | \s+ // ignore 8 | "(" return '(' 9 | ")" return ')' 10 | "-"?[0-9]+("."[0-9]+)?([eE][\-\+]?[0-9]+)? return 'DOUBLE_TOK' 11 | "POINT" return 'POINT' 12 | "LINESTRING" return 'LINESTRING' 13 | "POLYGON" return 'POLYGON' 14 | "MULTIPOINT" return 'MULTIPOINT' 15 | "MULTILINESTRING" return 'MULTILINESTRING' 16 | "MULTIPOLYGON" return 'MULTIPOLYGON' 17 | "GEOMETRYCOLLECTION" return 'GEOMETRYCOLLECTION' 18 | "," return 'COMMA' 19 | "EMPTY" return 'EMPTY' 20 | "M" return 'M' 21 | "Z" return 'Z' 22 | "ZM" return 'ZM' 23 | <> return 'EOF' 24 | . return "INVALID" 25 | 26 | /lex 27 | 28 | 29 | %start expressions 30 | 31 | %% /* language grammar */ 32 | 33 | expressions 34 | : point EOF 35 | { return $1; } 36 | | linestring EOF 37 | { return $1; } 38 | | polygon EOF 39 | { return $1; } 40 | | multipoint EOF 41 | { return $1; } 42 | | multilinestring EOF 43 | { return $1; } 44 | | multipolygon EOF 45 | { return $1; } 46 | | geometrycollection EOF 47 | { return $1; } 48 | ; 49 | 50 | coordinate 51 | : DOUBLE_TOK DOUBLE_TOK 52 | { $$ = new PointArray([ Number($1), Number($2) ]); } 53 | | DOUBLE_TOK DOUBLE_TOK DOUBLE_TOK 54 | { $$ = new PointArray([ Number($1), Number($2), Number($3) ]); } 55 | | DOUBLE_TOK DOUBLE_TOK DOUBLE_TOK DOUBLE_TOK 56 | { $$ = new PointArray([ Number($1), Number($2), Number($3), Number($4) ]); } 57 | ; 58 | 59 | ptarray 60 | : ptarray COMMA coordinate 61 | { $$ = $1.addPoint($3); } 62 | | coordinate 63 | { $$ = $1; } 64 | ; 65 | 66 | ring_list 67 | : ring_list COMMA ring 68 | { $$ = $1.addRing($3); } 69 | | ring 70 | { $$ = new RingList($1); } 71 | ; 72 | 73 | ring 74 | : '(' ptarray ')' 75 | { $$ = new Ring($2); } 76 | ; 77 | 78 | point 79 | : POINT '(' ptarray ')' 80 | { $$ = { "type": "Point", "coordinates": $3.data[0] }; } 81 | | POINT Z '(' ptarray ')' 82 | { $$ = { "type": "Point", "coordinates": $4.data[0], "properties": { z: true } }; } 83 | | POINT ZM '(' ptarray ')' 84 | { $$ = { "type": "Point", "coordinates": $4.data[0], "properties": { z: true, m: true } }; } 85 | | POINT M '(' ptarray ')' 86 | { $$ = { "type": "Point", "coordinates": $4.data[0], "properties": { m: true } }; } 87 | | POINT EMPTY 88 | { $$ = { "type": "Point", "coordinates": [ ] }; } 89 | ; 90 | 91 | point_untagged 92 | : coordinate 93 | { $$ = $1; } 94 | | '(' coordinate ')' 95 | { $$ = $2; } 96 | ; 97 | 98 | polygon_list 99 | : polygon_list COMMA polygon_untagged 100 | { $$ = $1.addPolygon($3); } 101 | | polygon_untagged 102 | { $$ = new PolygonList($1); } 103 | ; 104 | 105 | polygon_untagged 106 | : '(' ring_list ')' 107 | { $$ = $2; } 108 | ; 109 | 110 | 111 | point_list 112 | : point_list COMMA point_untagged 113 | { $$ = $1.addPoint($3); } 114 | | point_untagged 115 | { $$ = $1; } 116 | ; 117 | 118 | linestring 119 | : LINESTRING '(' point_list ')' 120 | { $$ = { "type": "LineString", "coordinates": $3.data }; } 121 | | LINESTRING Z '(' point_list ')' 122 | { $$ = { "type": "LineString", "coordinates": $4.data, "properties": { z: true } }; } 123 | | LINESTRING M '(' point_list ')' 124 | { $$ = { "type": "LineString", "coordinates": $4.data, "properties": { m: true } }; } 125 | | LINESTRING ZM '(' point_list ')' 126 | { $$ = { "type": "LineString", "coordinates": $4.data, "properties": { z: true, m: true } }; } 127 | | LINESTRING EMPTY 128 | { $$ = { "type": "LineString", "coordinates": [ ] }; } 129 | ; 130 | 131 | polygon 132 | : POLYGON '(' ring_list ')' 133 | { $$ = { "type": "Polygon", "coordinates": $3.toJSON() }; } 134 | | POLYGON Z '(' ring_list ')' 135 | { $$ = { "type": "Polygon", "coordinates": $4.toJSON(), "properties": { z: true } }; } 136 | | POLYGON M '(' ring_list ')' 137 | { $$ = { "type": "Polygon", "coordinates": $4.toJSON(), "properties": { m: true } }; } 138 | | POLYGON ZM '(' ring_list ')' 139 | { $$ = { "type": "Polygon", "coordinates": $4.toJSON(), "properties": { z: true, m: true } }; } 140 | | POLYGON EMPTY 141 | { $$ = { "type": "Polygon", "coordinates": [ ] }; } 142 | ; 143 | 144 | multipoint 145 | : MULTIPOINT '(' point_list ')' 146 | { $$ = { "type": "MultiPoint", "coordinates": $3.data }; } 147 | | MULTIPOINT Z '(' point_list ')' 148 | { $$ = { "type": "MultiPoint", "coordinates": $4.data, "properties": { z: true } }; } 149 | | MULTIPOINT M '(' point_list ')' 150 | { $$ = { "type": "MultiPoint", "coordinates": $4.data, "properties": { m: true } }; } 151 | | MULTIPOINT ZM '(' point_list ')' 152 | { $$ = { "type": "MultiPoint", "coordinates": $4.data, "properties": { z: true, m: true } }; } 153 | | MULTIPOINT EMPTY 154 | { $$ = { "type": "MultiPoint", "coordinates": [ ] }; } 155 | ; 156 | 157 | multilinestring 158 | : MULTILINESTRING '(' ring_list ')' 159 | { $$ = { "type": "MultiLineString", "coordinates": $3.toJSON() }; } 160 | | MULTILINESTRING Z '(' ring_list ')' 161 | { $$ = { "type": "MultiLineString", "coordinates": $4.toJSON(), "properties": { z: true } }; } 162 | | MULTILINESTRING M '(' ring_list ')' 163 | { $$ = { "type": "MultiLineString", "coordinates": $4.toJSON(), "properties": { m: true } }; } 164 | | MULTILINESTRING ZM '(' ring_list ')' 165 | { $$ = { "type": "MultiLineString", "coordinates": $4.toJSON(), "properties": { z: true, m: true } }; } 166 | | MULTILINESTRING EMPTY 167 | { $$ = { "type": "MultiLineString", "coordinates": [ ] }; } 168 | ; 169 | 170 | multipolygon 171 | : MULTIPOLYGON '(' polygon_list ')' 172 | { $$ = { "type": "MultiPolygon", "coordinates": $3.toJSON() }; } 173 | | MULTIPOLYGON Z '(' polygon_list ')' 174 | { $$ = { "type": "MultiPolygon", "coordinates": $4.toJSON(), "properties": { z: true } }; } 175 | | MULTIPOLYGON M '(' polygon_list ')' 176 | { $$ = { "type": "MultiPolygon", "coordinates": $4.toJSON(), "properties": { m: true } }; } 177 | | MULTIPOLYGON ZM '(' polygon_list ')' 178 | { $$ = { "type": "MultiPolygon", "coordinates": $4.toJSON(), "properties": { z: true, m: true } }; } 179 | | MULTIPOLYGON EMPTY 180 | { $$ = { "type": "MultiPolygon", "coordinates": [ ] }; } 181 | ; 182 | 183 | geometry 184 | : point 185 | { $$ = $1; } 186 | | linestring 187 | { $$ = $1; } 188 | | polygon 189 | { $$ = $1; } 190 | | multipoint 191 | { $$ = $1; } 192 | | multilinestring 193 | { $$ = $1; } 194 | | multipolygon 195 | { $$ = $1; } 196 | | geometrycollection 197 | { $$ = $1; } 198 | ; 199 | 200 | geometry_collection 201 | : geometry_collection COMMA geometry 202 | { $$ = $1.addGeometry($3); } 203 | | geometry 204 | { $$ = new GeometryList($1); } 205 | ; 206 | 207 | geometrycollection 208 | : GEOMETRYCOLLECTION '(' geometry_collection ')' 209 | { $$ = { "type": "GeometryCollection", "geometries": $3.toJSON() }; } 210 | | GEOMETRYCOLLECTION Z '(' geometry_collection ')' 211 | { $$ = { "type": "GeometryCollection", "geometries": $4.toJSON(), "properties": { z: true } }; } 212 | | GEOMETRYCOLLECTION M '(' geometry_collection ')' 213 | { $$ = { "type": "GeometryCollection", "geometries": $4.toJSON(), "properties": { m: true } }; } 214 | | GEOMETRYCOLLECTION ZM '(' geometry_collection ')' 215 | { $$ = { "type": "GeometryCollection", "geometries": $4.toJSON(), "properties": { z: true, m: true } }; } 216 | | GEOMETRYCOLLECTION EMPTY 217 | { $$ = { "type": "GeometryCollection", "geometries": [] }; } 218 | ; -------------------------------------------------------------------------------- /packages/wkt/test/geojson.test.js: -------------------------------------------------------------------------------- 1 | 2 | import test from 'tape'; 3 | import { geojsonToWKT } from '../src/index.js'; 4 | 5 | test('should exist', function (t) { 6 | t.plan(1); 7 | t.ok(geojsonToWKT); 8 | }); 9 | 10 | test('should turn a GeoJSON Point into WKT', function (t) { 11 | t.plan(1); 12 | 13 | const input = { 14 | type: 'Point', 15 | coordinates: [30, 10] 16 | }; 17 | 18 | const output = geojsonToWKT(input); 19 | 20 | t.deepEqual(output, 'POINT (30 10)'); 21 | }); 22 | 23 | test('should convert a POINT with Z', function (t) { 24 | t.plan(1); 25 | 26 | const input = { 27 | type: 'Point', 28 | coordinates: [30, 10, 10] 29 | }; 30 | 31 | const output = geojsonToWKT(input); 32 | 33 | t.deepEqual(output, 'POINT Z (30 10 10)'); 34 | }); 35 | 36 | test('should convert a POINT with M (nonstandard)', function (t) { 37 | t.plan(1); 38 | 39 | const input = { 40 | properties: { m: true }, 41 | type: 'Point', 42 | coordinates: [30, 10, 10] 43 | }; 44 | 45 | const output = geojsonToWKT(input); 46 | 47 | t.deepEqual(output, 'POINT M (30 10 10)'); 48 | }); 49 | 50 | test('should convert a POINT with Z and M', function (t) { 51 | t.plan(1); 52 | 53 | const input = { 54 | type: 'Point', 55 | coordinates: [30, 10, 10, 12] 56 | }; 57 | 58 | const output = geojsonToWKT(input); 59 | 60 | t.deepEqual(output, 'POINT ZM (30 10 10 12)'); 61 | }); 62 | 63 | test('should convert an empty POINT', function (t) { 64 | t.plan(1); 65 | 66 | const input = { 67 | type: 'Point', 68 | coordinates: [] 69 | }; 70 | 71 | const output = geojsonToWKT(input); 72 | 73 | t.deepEqual(output, 'POINT EMPTY'); 74 | }); 75 | 76 | test('should convert a POLYGON', function (t) { 77 | t.plan(1); 78 | 79 | const input = { 80 | type: 'Polygon', 81 | coordinates: [[[30, 10], [20, 20], [30, 20]]] 82 | }; 83 | 84 | const output = geojsonToWKT(input); 85 | 86 | t.deepEqual(output, 'POLYGON ((30 10, 20 20, 30 20))'); 87 | }); 88 | 89 | test('should convert a POLYGON with Z', function (t) { 90 | t.plan(1); 91 | 92 | const input = { 93 | type: 'Polygon', 94 | coordinates: [[[30, 10, 1], [20, 20, 2], [30, 20, 3]]] 95 | }; 96 | 97 | const output = geojsonToWKT(input); 98 | 99 | t.deepEqual(output, 'POLYGON Z ((30 10 1, 20 20 2, 30 20 3))'); 100 | }); 101 | 102 | test('should convert a POLYGON with ZM', function (t) { 103 | t.plan(1); 104 | 105 | const input = { 106 | type: 'Polygon', 107 | coordinates: [[[30, 10, 1, 3], [20, 20, 2, 2], [30, 20, 3, 1]]] 108 | }; 109 | 110 | const output = geojsonToWKT(input); 111 | 112 | t.deepEqual(output, 'POLYGON ZM ((30 10 1 3, 20 20 2 2, 30 20 3 1))'); 113 | }); 114 | 115 | test('should convert a POLYGON with M (nonstandard)', function (t) { 116 | t.plan(1); 117 | 118 | const input = { 119 | properties: { m: true }, 120 | type: 'Polygon', 121 | coordinates: [[[30, 10, 1], [20, 20, 2], [30, 20, 3]]] 122 | }; 123 | 124 | const output = geojsonToWKT(input); 125 | 126 | t.deepEqual(output, 'POLYGON M ((30 10 1, 20 20 2, 30 20 3))'); 127 | }); 128 | 129 | test('should convert an EMPTY POLYGON', function (t) { 130 | t.plan(1); 131 | 132 | const input = { 133 | type: 'Polygon', 134 | coordinates: [] 135 | }; 136 | 137 | const output = geojsonToWKT(input); 138 | 139 | t.deepEqual(output, 'POLYGON EMPTY'); 140 | }); 141 | 142 | test('should convert a MULTIPOINT', function (t) { 143 | t.plan(1); 144 | 145 | const input = { 146 | type: 'MultiPoint', 147 | coordinates: [[30, 10], [20, 20], [30, 20]] 148 | }; 149 | 150 | const output = geojsonToWKT(input); 151 | 152 | t.deepEqual(output, 'MULTIPOINT (30 10, 20 20, 30 20)'); 153 | }); 154 | 155 | test('should convert a MULTIPOINT with Z', function (t) { 156 | t.plan(1); 157 | 158 | const input = { 159 | type: 'MultiPoint', 160 | coordinates: [[30, 10, 1], [20, 20, 2], [30, 20, 3]] 161 | }; 162 | 163 | const output = geojsonToWKT(input); 164 | 165 | t.deepEqual(output, 'MULTIPOINT Z (30 10 1, 20 20 2, 30 20 3)'); 166 | }); 167 | 168 | test('should convert a MULTIPOINT with ZM', function (t) { 169 | t.plan(1); 170 | 171 | const input = { 172 | type: 'MultiPoint', 173 | coordinates: [[30, 10, 1, 2], [20, 20, 3, 4], [30, 20, 5, 6]] 174 | }; 175 | 176 | const output = geojsonToWKT(input); 177 | 178 | t.deepEqual(output, 'MULTIPOINT ZM (30 10 1 2, 20 20 3 4, 30 20 5 6)'); 179 | }); 180 | 181 | test('should convert a MULTIPOINT with M (nonstandard)', function (t) { 182 | t.plan(1); 183 | 184 | const input = { 185 | properties: { m: true }, 186 | type: 'MultiPoint', 187 | coordinates: [[30, 10, 1], [20, 20, 2], [30, 20, 3]] 188 | }; 189 | 190 | const output = geojsonToWKT(input); 191 | 192 | t.deepEqual(output, 'MULTIPOINT M (30 10 1, 20 20 2, 30 20 3)'); 193 | }); 194 | 195 | test('should convert an EMPTY MULTIPOINT', function (t) { 196 | t.plan(1); 197 | 198 | const input = { 199 | type: 'MultiPoint', 200 | coordinates: [] 201 | }; 202 | 203 | const output = geojsonToWKT(input); 204 | 205 | t.deepEqual(output, 'MULTIPOINT EMPTY'); 206 | }); 207 | 208 | test('should convert a LINESTRING with Z', function (t) { 209 | t.plan(1); 210 | 211 | const input = { 212 | type: 'LineString', 213 | coordinates: [[30, 10, 2], [20, 20, 1], [30, 20, 0]] 214 | }; 215 | 216 | const output = geojsonToWKT(input); 217 | 218 | t.deepEqual(output, 'LINESTRING Z (30 10 2, 20 20 1, 30 20 0)'); 219 | }); 220 | 221 | test('should convert a LINESTRING with ZM', function (t) { 222 | t.plan(1); 223 | 224 | const input = { 225 | type: 'LineString', 226 | coordinates: [[30, 10, 1, 2], [20, 20, 3, 4], [30, 20, 5, 6]] 227 | }; 228 | 229 | const output = geojsonToWKT(input); 230 | 231 | t.deepEqual(output, 'LINESTRING ZM (30 10 1 2, 20 20 3 4, 30 20 5 6)'); 232 | }); 233 | 234 | test('should convert a LINESTRING with M (nonstandard)', function (t) { 235 | t.plan(1); 236 | 237 | const input = { 238 | properties: { m: true }, 239 | type: 'LineString', 240 | coordinates: [[30, 10, 1], [20, 20, 2], [30, 20, 3]] 241 | }; 242 | 243 | const output = geojsonToWKT(input); 244 | 245 | t.deepEqual(output, 'LINESTRING M (30 10 1, 20 20 2, 30 20 3)'); 246 | }); 247 | 248 | test('should convert an empty LINESTRING', function (t) { 249 | t.plan(1); 250 | 251 | const input = { 252 | type: 'LineString', 253 | coordinates: [] 254 | }; 255 | 256 | const output = geojsonToWKT(input); 257 | 258 | t.deepEqual(output, 'LINESTRING EMPTY'); 259 | }); 260 | 261 | test('should convert a LINESTRING', function (t) { 262 | t.plan(1); 263 | 264 | const input = { 265 | type: 'LineString', 266 | coordinates: [[30, 10], [20, 20], [30, 20]] 267 | }; 268 | 269 | const output = geojsonToWKT(input); 270 | 271 | t.deepEqual(output, 'LINESTRING (30 10, 20 20, 30 20)'); 272 | }); 273 | 274 | test('should convert a MULTILINESTRING', function (t) { 275 | t.plan(1); 276 | 277 | const input = { 278 | type: 'MultiLineString', 279 | coordinates: [[[30, 10], [20, 20], [30, 20]]] 280 | }; 281 | 282 | const output = geojsonToWKT(input); 283 | 284 | t.deepEqual(output, 'MULTILINESTRING ((30 10, 20 20, 30 20))'); 285 | }); 286 | 287 | test('should convert a MULTILINESTRING with Z', function (t) { 288 | t.plan(1); 289 | 290 | const input = { 291 | type: 'MultiLineString', 292 | coordinates: [[[30, 10, 1], [20, 20, 2], [30, 20, 3]]] 293 | }; 294 | 295 | const output = geojsonToWKT(input); 296 | 297 | t.deepEqual(output, 'MULTILINESTRING Z ((30 10 1, 20 20 2, 30 20 3))'); 298 | }); 299 | 300 | test('should convert a MULTILINESTRING with Z and M', function (t) { 301 | t.plan(1); 302 | 303 | const input = { 304 | type: 'MultiLineString', 305 | coordinates: [[[30, 10, 1, 2], [20, 20, 3, 4], [30, 20, 5, 6]]] 306 | }; 307 | 308 | const output = geojsonToWKT(input); 309 | 310 | t.deepEqual(output, 'MULTILINESTRING ZM ((30 10 1 2, 20 20 3 4, 30 20 5 6))'); 311 | }); 312 | 313 | test('should convert a MULTILINESTRING with M (nonstandard)', function (t) { 314 | t.plan(1); 315 | 316 | const input = { 317 | properties: { m: true }, 318 | type: 'MultiLineString', 319 | coordinates: [[[30, 10, 1], [20, 20, 2], [30, 20, 3]]] 320 | }; 321 | 322 | const output = geojsonToWKT(input); 323 | 324 | t.deepEqual(output, 'MULTILINESTRING M ((30 10 1, 20 20 2, 30 20 3))'); 325 | }); 326 | 327 | test('should convert an empty MULTILINESTRING', function (t) { 328 | t.plan(1); 329 | 330 | const input = { 331 | type: 'MultiLineString', 332 | coordinates: [] 333 | }; 334 | 335 | const output = geojsonToWKT(input); 336 | 337 | t.deepEqual(output, 'MULTILINESTRING EMPTY'); 338 | }); 339 | 340 | test('should convert a MULTIPOLYGON', function (t) { 341 | t.plan(1); 342 | 343 | const input = { 344 | type: 'MultiPolygon', 345 | coordinates: [ 346 | [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], 347 | [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], 348 | [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]] 349 | ] 350 | }; 351 | 352 | const output = geojsonToWKT(input); 353 | 354 | t.deepEqual(output, 'MULTIPOLYGON (((102 2, 103 2, 103 3, 102 3, 102 2)), ((100 0, 101 0, 101 1, 100 1, 100 0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))'); 355 | }); 356 | 357 | test('should convert a MULTIPOLYGON with Z', function (t) { 358 | t.plan(1); 359 | 360 | const input = { 361 | type: 'MultiPolygon', 362 | coordinates: [ 363 | [[[102.0, 2.0, 1], [103.0, 2.0, 2], [103.0, 3.0, 3], [102.0, 3.0, 4], [102.0, 2.0, 5]]], 364 | [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], 365 | [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]] 366 | ] 367 | }; 368 | 369 | const output = geojsonToWKT(input); 370 | 371 | t.deepEqual(output, 'MULTIPOLYGON Z (((102 2 1, 103 2 2, 103 3 3, 102 3 4, 102 2 5)), ((100 0, 101 0, 101 1, 100 1, 100 0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))'); 372 | }); 373 | 374 | test('should convert a MULTIPOLYGON with Z and M', function (t) { 375 | t.plan(1); 376 | 377 | const input = { 378 | type: 'MultiPolygon', 379 | coordinates: [ 380 | [[[102.0, 2.0, 1, 2], [103.0, 2.0, 3, 4], [103.0, 3.0, 5, 6], [102.0, 3.0, 7, 8], [102.0, 2.0, 9, 10]]], 381 | [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], 382 | [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]] 383 | ] 384 | }; 385 | 386 | const output = geojsonToWKT(input); 387 | 388 | t.deepEqual(output, 'MULTIPOLYGON ZM (((102 2 1 2, 103 2 3 4, 103 3 5 6, 102 3 7 8, 102 2 9 10)), ((100 0, 101 0, 101 1, 100 1, 100 0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))'); 389 | }); 390 | 391 | test('should convert a MULTIPOLYGON with M (non standard)', function (t) { 392 | t.plan(1); 393 | 394 | const input = { 395 | type: 'MultiPolygon', 396 | properties: { m: true }, 397 | coordinates: [ 398 | [[[102.0, 2.0, 1], [103.0, 2.0, 2], [103.0, 3.0, 3], [102.0, 3.0, 4], [102.0, 2.0, 5]]], 399 | [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], 400 | [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]] 401 | ] 402 | }; 403 | 404 | const output = geojsonToWKT(input); 405 | 406 | t.deepEqual(output, 'MULTIPOLYGON M (((102 2 1, 103 2 2, 103 3 3, 102 3 4, 102 2 5)), ((100 0, 101 0, 101 1, 100 1, 100 0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))'); 407 | }); 408 | 409 | test('should convert an EMPTY MULTIPOLYGON', function (t) { 410 | t.plan(1); 411 | 412 | const input = { 413 | type: 'MultiPolygon', 414 | coordinates: [] 415 | }; 416 | 417 | const output = geojsonToWKT(input); 418 | 419 | t.deepEqual(output, 'MULTIPOLYGON EMPTY'); 420 | }); 421 | 422 | test('should convert a Geometry Collection', function (t) { 423 | t.plan(1); 424 | 425 | const input = { 426 | type: 'GeometryCollection', 427 | geometries: [ 428 | { 429 | type: 'Point', 430 | coordinates: [100.0, 0.0] 431 | }, 432 | { 433 | type: 'LineString', 434 | coordinates: [[101.0, 0.0], [102.0, 1.0]] 435 | } 436 | ] 437 | }; 438 | 439 | const output = geojsonToWKT(input); 440 | 441 | t.deepEqual(output, 'GEOMETRYCOLLECTION(POINT (100 0), LINESTRING (101 0, 102 1))'); 442 | }); 443 | 444 | test('should fail a conversion on an unknown type', function (t) { 445 | t.plan(1); 446 | 447 | const input = { 448 | type: 'MultiPolygonLikeThingy', 449 | coordinates: [] 450 | }; 451 | 452 | try { 453 | geojsonToWKT(input); 454 | } catch (err) { 455 | const error = err.toString(); 456 | t.deepEqual(error, 'Error: Unknown Type: MultiPolygonLikeThingy'); 457 | } 458 | }); 459 | -------------------------------------------------------------------------------- /rollup.esm.config.js: -------------------------------------------------------------------------------- 1 | import config from './rollup.umd.config'; 2 | 3 | config.output.format = 'esm'; 4 | config.output.file = config.output.file.replace('umd.js', 'esm.js'); 5 | delete config.output.name; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /rollup.umd.config.js: -------------------------------------------------------------------------------- 1 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 2 | import json from '@rollup/plugin-json'; 3 | import babel from '@rollup/plugin-babel'; 4 | 5 | const path = require('path'); 6 | 7 | /** 8 | * Since Rollup runs inside each package 9 | * we can just get the current package we are bundling. 10 | */ 11 | const pkg = require(path.join(process.cwd(), 'package.json')); 12 | 13 | const copyright = `/* @preserve 14 | * ${pkg.name} - v${pkg.version} - ${pkg.license} 15 | * Copyright (c) 2012-${new Date().getFullYear()} Environmental Systems Research Institute, Inc. 16 | * ${new Date().toString()} 17 | */`; 18 | 19 | /** 20 | * and dig out its name. 21 | */ 22 | const { name } = pkg; 23 | const sanitizedName = name.replace('@terraformer/', 't-'); 24 | 25 | export default { 26 | input: 'src/index.js', // resolved by our plugin 27 | plugins: [ 28 | nodeResolve(), 29 | json(), 30 | babel({ 31 | rootMode: 'upward' 32 | }) 33 | ], 34 | output: { 35 | file: `./dist/${sanitizedName}.umd.js`, 36 | banner: copyright, 37 | format: 'umd', 38 | name: 'Terraformer', 39 | extend: true 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /test-helper.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | const { lstatSync, readdirSync } = require('fs'); 3 | const { join } = require('path'); 4 | 5 | const isDirectory = source => lstatSync(source).isDirectory(); 6 | 7 | const getDirectories = source => 8 | readdirSync(source).map(name => join(source, name)).filter(isDirectory); 9 | 10 | const getTestFiles = source => 11 | readdirSync(source).filter(filename => filename.endsWith('test.js')); 12 | 13 | // get all the folders insides the packages/ folder 14 | const packages = getDirectories('packages').reverse(); 15 | 16 | for (var i = 0; i < packages.length; i++) { 17 | // pluck out all the files with tests in each package's test directory 18 | const tests = getTestFiles(`${packages[i]}/test`); 19 | for (var j = 0; j < tests.length; j++) { 20 | // use babel to transpile the source and pass each test to the Node.js CLI 21 | exec(`babel-node ${packages[i]}/test/${tests[j]} [ babelify --presets @babel/preset-env ]`, function (err, res) { 22 | console.log(res); 23 | if (err) throw Error(err); 24 | }); 25 | } 26 | } 27 | --------------------------------------------------------------------------------