├── .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 |
--------------------------------------------------------------------------------