├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── benchmarks └── benchmarks.js ├── bower.json ├── examples ├── .gitignore ├── README.md ├── package.json └── terraformer-arcgis-fileparser.js ├── index.d.ts ├── jasmine.json ├── package-lock.json ├── package.json ├── release.sh ├── spec └── arcgisSpec.js ├── terraformer-arcgis-parser.js ├── test.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore complexity output 2 | complexity.xml 3 | 4 | # Ignore Coverage output 5 | coverage 6 | 7 | # Ignore Grunt temp files 8 | .grunt 9 | 10 | # Mac 11 | .DS_Store 12 | Icon 13 | ._* 14 | .Spotlight-V100 15 | # SublimeText 16 | /*.sublime-project 17 | *.sublime-workspace 18 | 19 | #Node 20 | node_modules 21 | npm-debug.log 22 | 23 | # built 24 | terraformer-arcgis-parser.min.js 25 | 26 | # typescript 27 | typings/** 28 | test.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | notifications: 4 | email: false 5 | before_install: npm install -g grunt-cli 6 | node_js: 7 | - "10" 8 | cache: 9 | directories: 10 | - node_modules 11 | addons: 12 | chrome: stable -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [unreleased] 6 | 7 | ## [1.1.0] - 2018-07-09 8 | 9 | ### Added 10 | 11 | * ArcGIS Extents/Envelopes are now converted to GeoJSON polygons 🙏CorinChappy🙏 12 | * great new command line demo app 🙏gseyffert🙏 13 | * an appropriate CRS is appended to output GeoJSON when web mercator geometries are encountered. 14 | 15 | ### Changed 16 | 17 | * Ensure ring-order of GeoJSON is compliant with RFC 7946 18 | * web mercator northings/eastings are no longer reprojected during conversion. 19 | * TypeScript support files updated 🙏JeffJacobson🙏 20 | 21 | ## [1.0.5] - 2016-08-16 22 | 23 | ### Fixed 24 | * Check for GeoJSON feature `id`s before assigning to output [#24](https://github.com/Esri/terraformer-arcgis-parser/pull/24) 25 | * correct conversion of polygons with outer rings not containing holes [#28](https://github.com/Esri/terraformer-arcgis-parser/pull/28) 26 | 27 | ### Added 28 | * typings for TypeScript folks (thx [@JeffJacobson](https://github.com/JeffJacobson)) [#34](https://github.com/Esri/terraformer-arcgis-parser/pull/34) 29 | 30 | ## [1.0.4] - 2014-06-17 31 | 32 | ### Fixed 33 | * Account for breaking change in `Terraformer` 34 | 35 | ### Added 36 | * support for `z` and `m` conversion 37 | 38 | ## [1.0.3] - 2015-02-24 39 | 40 | ### Fixed 41 | * valid output on both ends of conversion [#19](https://github.com/Esri/terraformer-arcgis-parser/issues/19) 42 | 43 | ## [1.0.2] - 2014-02-10 44 | 45 | ### Added 46 | * `parseCompressedGeometry()` [#10](https://github.com/Esri/terraformer-arcgis-parser/issues/10) 47 | 48 | ### Fixed 49 | * `parse()` and `convert()` now close polygons during conversion. [#9](https://github.com/Esri/terraformer-arcgis-parser/issues/9) 50 | * `parse()` now handles `compressedGeometry` 51 | 52 | ## [1.0.1] - 2013-12-04 53 | 54 | ### Fixed 55 | * `ReferenceError: sr is not defined` in `ArcGIS.convert()` 56 | 57 | ## [1.0.0] - 2013-11-12 58 | 59 | Initial Release. Also available on NPM and Bower 60 | 61 | ``` 62 | npm install terraformer-arcgis-parser 63 | bower install terraformer-arcgis-parser 64 | ``` 65 | 66 | [unreleased]: https://github.com/Esri/terraformer-arcgis-parser/compare/v1.1.0...HEAD 67 | [1.1.0]: https://github.com/Esri/terraformer-arcgis-parser/compare/v1.0.5...v1.1.0 68 | [1.0.5]: https://github.com/Esri/terraformer-arcgis-parser/compare/v1.0.4...v1.0.5 69 | [1.0.4]: https://github.com/Esri/terraformer-arcgis-parser/compare/v1.0.3...v1.0.4 70 | [1.0.3]: https://github.com/Esri/terraformer-arcgis-parser/compare/v1.0.2...v1.0.3 71 | [1.0.2]: https://github.com/Esri/terraformer-arcgis-parser/compare/v1.0.1...v1.0.2 72 | [1.0.1]: https://github.com/Esri/terraformer-arcgis-parser/compare/v1.0.0...v1.0.1 73 | [1.0.0]: https://github.com/Esri/terraformer-arcgis-parser/releases/tag/v1.0.0 74 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports = function (grunt) { 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | 7 | meta: { 8 | banner: '/*! Terraformer ArcGIS Parser - <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 9 | '* https://github.com/esri/terraformer-arcgis-parser\n' + 10 | '* Copyright (c) 2013-<%= grunt.template.today("yyyy") %> Esri, Inc.\n' + 11 | '* Licensed MIT */' 12 | }, 13 | 14 | uglify: { 15 | options: { 16 | report: 'gzip', 17 | banner: '<%= meta.banner %>' 18 | }, 19 | arcgis: { 20 | src: ["terraformer-arcgis-parser.js"], 21 | dest: 'terraformer-arcgis-parser.min.js' 22 | } 23 | }, 24 | 25 | jasmine: { 26 | coverage: { 27 | src: [ 28 | "terraformer-arcgis-parser.js" 29 | ], 30 | options: { 31 | specs: 'spec/*Spec.js', 32 | helpers:[ 33 | "node_modules/terraformer/terraformer.js" 34 | ], 35 | //keepRunner: true, 36 | outfile: 'SpecRunner.html', 37 | // template: require('grunt-template-jasmine-istanbul'), 38 | // templateOptions: { 39 | // coverage: './coverage/coverage.json', 40 | // report: './coverage', 41 | // thresholds: { 42 | // lines: 80, 43 | // statements: 80, 44 | // branches: 75, 45 | // functions: 80 46 | // } 47 | // } 48 | } 49 | } 50 | }, 51 | 52 | complexity: { 53 | generic: { 54 | src: [ 'terraformer-arcgis-parser.js' ], 55 | options: { 56 | jsLintXML: 'complexity.xml', // create XML JSLint-like report 57 | errorsOnly: false, // show only maintainability errors 58 | cyclomatic: 6, 59 | halstead: 15, 60 | maintainability: 65 61 | } 62 | } 63 | } 64 | }); 65 | 66 | grunt.loadNpmTasks('grunt-contrib-uglify'); 67 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 68 | grunt.loadNpmTasks('grunt-complexity'); 69 | 70 | grunt.registerTask('test', ['jasmine']); 71 | grunt.registerTask('version', ['test', 'uglify']); 72 | grunt.registerTask('default', ['test']); 73 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2018 Esri 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important! 2 | 3 | This repo is part of the Terraformer project which has been archived. See https://github.com/Esri/terraformer#important for more details. 4 | 5 | # Terraformer ArcGIS JSON Parser 6 | 7 | [![Build Status](https://travis-ci.org/Esri/terraformer-arcgis-parser.svg?branch=master)](https://travis-ci.org/Esri/terraformer-arcgis-parser) 8 | 9 | > Two way conversion between [GeoJSON](http://geojson.org/geojson-spec.html) and [ArcGIS Geometry](http://help.arcgis.com/en/arcgisserver/10.0/apis/rest/geometry.html). 10 | 11 | ## Installing 12 | 13 | ### Node.js 14 | 15 | $ npm install terraformer-arcgis-parser 16 | 17 | ### Browser 18 | 19 | In the browser, [Terraformer](http://github.com/esri/terraformer) is required. 20 | 21 | ## Documentation 22 | 23 | For full documentation check https://github.com/Esri/terraformer/blob/master/docs/arcgis-parser.md. 24 | 25 | ### Node.js 26 | ```js 27 | var ArcGIS = require('terraformer-arcgis-parser'); 28 | 29 | // parse ArcGIS JSON, convert it to a Terraformer.Primitive (GeoJSON) 30 | var primitive = ArcGIS.parse({ 31 | 'x':-122.6764, 32 | 'y':45.5165, 33 | 'spatialReference': { 34 | 'wkid': 4326 35 | } 36 | }); 37 | 38 | // take a Terraformer.Primitive or GeoJSON and convert it back to ArcGIS JSON 39 | var point = ArcGIS.convert({ 40 | 'type': "Point", 41 | 'coordinates': [45.5165, -122.6764] 42 | }); 43 | ``` 44 | ### Browser 45 | ```html 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 68 | ``` 69 | ### TypeScript 70 | ``` 71 | import arcgis = require("terraformer-arcgis-parser"); 72 | ``` 73 | 74 | ## Issues 75 | 76 | Find a bug or want to request a new feature? Please let us know by submitting an issue. 77 | 78 | ## Contributing 79 | 80 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 81 | 82 | ## Licensing 83 | 84 | Copyright © 2013-2018 Esri 85 | 86 | A copy of the license is available in the repository's [LICENSE](./LICENSE) file. 87 | -------------------------------------------------------------------------------- /benchmarks/benchmarks.js: -------------------------------------------------------------------------------- 1 | ArcGIS = require('../terraformer-arcgis-parser'); 2 | 3 | var Benchmark = require('benchmark'); 4 | 5 | var suite = new Benchmark.Suite(); 6 | 7 | // add tests 8 | suite 9 | 10 | .add('ArcGIS Point to GeoJSON Point', function() { 11 | ArcGIS.parse({ 12 | "x": -66.796875, 13 | "y": 20.0390625, 14 | "spatialReference": { 15 | "wkid": 4326 16 | } 17 | }); 18 | }) 19 | 20 | .add('ArcGIS Polyline to GeoJSON LineString', function() { 21 | ArcGIS.parse({ 22 | "paths": [ 23 | [ [6.6796875,47.8125],[-65.390625,52.3828125],[-52.3828125,42.5390625] ] 24 | ], 25 | "spatialReference": { 26 | "wkid": 4326 27 | } 28 | }); 29 | }) 30 | 31 | .add('ArcGIS Polygon to GeoJSON Polygon', function() { 32 | ArcGIS.parse({ 33 | "rings": [ 34 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 35 | ], 36 | "spatialReference": { 37 | "wkid": 4326 38 | } 39 | }); 40 | }) 41 | 42 | .add('ArcGIS Multipoint to GeoJSON Multipoint', function(){ 43 | ArcGIS.parse({ 44 | "points":[ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625] ], 45 | "spatialReference":{ 46 | "wkid":4326 47 | } 48 | }); 49 | }) 50 | 51 | .add('ArcGIS Polyline w/ 2 Paths to GeoJSON MultiLineString', function(){ 52 | ArcGIS.parse({ 53 | "paths":[ 54 | [ [41.8359375,71.015625],[56.953125,33.75] ], 55 | [ [21.796875,36.5625],[41.8359375,71.015625] ] 56 | ], 57 | "spatialReference":{ 58 | "wkid":4326 59 | } 60 | }); 61 | }) 62 | 63 | .add('ArcGIS Polygon w/ 2 Rings to GeoJSON MultiPolygon', function(){ 64 | ArcGIS.parse({ 65 | "rings":[ 66 | [[-122.63,45.52],[-122.57,45.53],[-122.52,45.50],[-122.49,45.48],[-122.64,45.49],[-122.63,45.52],[-122.63,45.52]], 67 | [[-83,35],[-74,35],[-74,41],[-83,41],[-83,35]] 68 | ], 69 | "spatialReference": { 70 | "wkid":4326 71 | } 72 | }); 73 | }) 74 | 75 | .add('ArcGIS Polygon w/ Holes into GeoJSON MultiPolygon', function(){ 76 | ArcGIS.parse({ 77 | "type":"polygon", 78 | "rings":[ 79 | [ [-100.74462180954974,39.95017165502381],[-94.50439384003792,39.91647453608879],[-94.41650267263967,34.89313438177965],[-100.78856739324887,34.85708140996771],[-100.74462180954974,39.95017165502381] ], 80 | [ [-99.68993678392353,39.341088433448896],[-99.68993678392353,38.24507658785885],[-98.67919734199646,37.86444431771113],[-98.06395917020868,38.210554846669694],[-98.06395917020868,39.341088433448896],[-99.68993678392353,39.341088433448896] ], 81 | [ [-96.83349180978595,37.23732027507514],[-97.31689323047635,35.967330282988534],[-96.5698183075912,35.57512048069255],[-95.42724211456674,36.357601429255965],[-96.83349180978595,37.23732027507514] ], 82 | [ [-101.4916967324349,38.24507658785885],[-101.44775114873578,36.073960493943744],[-103.95263145328033,36.03843312329154],[-103.68895795108557,38.03770050767439],[-101.4916967324349,38.24507658785885] ] 83 | ], 84 | "spatialReference":{ 85 | "wkid":4326 86 | } 87 | }); 88 | }) 89 | 90 | .add('ArcGIS Feature to GeoJSON Feature', function(){ 91 | ArcGIS.parse({ 92 | "geometry": { 93 | "rings": [ 94 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 95 | ], 96 | "spatialReference": { 97 | "wkid": 4326 98 | } 99 | }, 100 | "attributes": { 101 | "foo": "bar" 102 | } 103 | }); 104 | }) 105 | 106 | .add('GeoJSON Point to ArcGIS Point', function() { 107 | ArcGIS.convert({ 108 | "type": "Point", 109 | "coordinates": [-58.7109375,47.4609375] 110 | }); 111 | }) 112 | 113 | .add('GeoJSON LineString to ArcGIS Polyline', function() { 114 | ArcGIS.convert({ 115 | "type": "LineString", 116 | "coordinates": [ [21.4453125,-14.0625],[33.3984375,-20.7421875],[38.3203125,-24.609375] ] 117 | }); 118 | }) 119 | 120 | .add('GeoJSON Polygon to ArcGIS Polygon', function() { 121 | ArcGIS.convert({ 122 | "type": "Polygon", 123 | "coordinates": [ 124 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 125 | ] 126 | }); 127 | }) 128 | 129 | .add('GeoJSON Polygon w/ hole to ArcGIS Polygon', function(){ 130 | ArcGIS.convert({ 131 | "type": "Polygon", 132 | "coordinates": [ 133 | [ [100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0] ], 134 | [ [100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2] ] 135 | ] 136 | }); 137 | }) 138 | 139 | .add('GeoJSON Multipoint to ArcGIS Multipoint', function(){ 140 | ArcGIS.convert({ 141 | "type": "MultiPoint", 142 | "coordinates": [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625] ] 143 | }); 144 | }) 145 | 146 | .add('GeoJSON MultiLineString to ArcGIS Polyline', function(){ 147 | ArcGIS.convert({ 148 | "type": "MultiLineString", 149 | "coordinates": [ 150 | [ [41.8359375,71.015625],[56.953125,33.75] ], 151 | [ [21.796875,36.5625],[47.8359375,71.015625] ] 152 | ] 153 | }); 154 | }) 155 | 156 | .add('GeoJSON MultiPolygon to ArcGIS MultiPolygon', function(){ 157 | ArcGIS.convert({ 158 | "type": "MultiPolygon", 159 | "coordinates": [ 160 | [ 161 | [ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ] 162 | ], 163 | [ 164 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] 165 | ] 166 | ] 167 | }); 168 | }) 169 | 170 | .add('GeoJSON MultiPolygon w/ hole to ArcGIS Polygon', function(){ 171 | ArcGIS.convert({ 172 | "type": "MultiPolygon", 173 | "coordinates": [ 174 | [ 175 | [ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ] 176 | ], 177 | [ 178 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ], 179 | [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ] 180 | ] 181 | ] 182 | }); 183 | }) 184 | 185 | .add('GeoJSON Feature to ArcGIS Feature', function(){ 186 | ArcGIS.convert({ 187 | "type":"Feature", 188 | "geometry": { 189 | "type": "Polygon", 190 | "coordinates": [ 191 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 192 | ] 193 | }, 194 | "properties": { 195 | "foo":"bar" 196 | } 197 | }); 198 | }) 199 | 200 | .add('GeoJSON Feature Collection to Array of ArcGIS Features', function(){ 201 | ArcGIS.convert({ 202 | "type": "FeatureCollection", 203 | "features": [{ 204 | "type": "Feature", 205 | "geometry": { 206 | "type": "Point", 207 | "coordinates": [102.0, 0.5] 208 | }, 209 | "properties": { 210 | "prop0": "value0" 211 | } 212 | }, { 213 | "type": "Feature", 214 | "geometry": { 215 | "type": "LineString", 216 | "coordinates": [ 217 | [102.0, 0.0],[103.0, 1.0],[104.0, 0.0],[105.0, 1.0] 218 | ] 219 | }, 220 | "properties": { 221 | "prop0": "value0" 222 | } 223 | }, { 224 | "type": "Feature", 225 | "geometry": { 226 | "type": "Polygon", 227 | "coordinates": [ 228 | [ [100.0, 0.0],[101.0, 0.0],[101.0, 1.0],[100.0, 1.0],[100.0, 0.0] ] 229 | ] 230 | }, 231 | "properties": { 232 | "prop0": "value0" 233 | } 234 | }] 235 | }); 236 | }) 237 | 238 | .add('GeoJSON Geometry Collection to Array of ArcGIS Geometries', function(){ 239 | ArcGIS.convert({ 240 | "type" : "GeometryCollection", 241 | "geometries" : [{ 242 | "type" : "Polygon", 243 | "coordinates" : [[[-95, 43], [-95, 50], [-90, 50], [-91, 42], [-95, 43]]] 244 | }, { 245 | "type" : "LineString", 246 | "coordinates" : [[-89, 42], [-89, 50], [-80, 50], [-80, 42]] 247 | }, { 248 | "type" : "Point", 249 | "coordinates" : [-94, 46] 250 | }] 251 | }); 252 | }) 253 | 254 | .on('cycle', function(event) { 255 | console.log(String(event.target)); 256 | }) 257 | 258 | // .on('complete', function() { 259 | // console.log('Fastest is ' + this.filter('fastest').pluck('name')); 260 | // }) 261 | 262 | .run({ 'async': false }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terraformer-arcgis-parser", 3 | "main": "terraformer-arcgis-parser.min.js", 4 | "ignore" : ["versions", "benchmarks"], 5 | "dependencies": { 6 | "terraformer": "~1.0.4" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.geojson 2 | *.json -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Terraformer ArcGIS File Parser 2 | 3 | > Convert .geojson and Esri .json back and forth. 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ git clone https://github.com/Esri/terraformer-arcgis-parser 9 | $ cd ./terraformer-arcgis-parser/examples 10 | $ npm install 11 | ``` 12 | 13 | ## Use 14 | 15 | ``` 16 | $ npm run convert bars.geojson 17 | > bars.json 18 | 19 | $ npm run parse colorado.json 20 | > colorado.geojson 21 | ``` 22 | 23 | sample [`.geojson`](https://raw.githubusercontent.com/benbalter/dc-wifi-social/master/bars.geojson) / sample geoservices[ `.json`](http://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/3/query?where=STATE_NAME+%3D+%27Colorado%27&maxAllowableOffset=0.01&outSR=4326&f=pjson) 24 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terraformer-arcgis-fileparser", 3 | "version": "1.0.0", 4 | "description": "A utility that uses terraformer-arcgis-parser to convert .geojson files to ArcGIS formatted .json files & vice-versa.", 5 | "main": "terraformer-arcgis-fileparser.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "convert": "node ./terraformer-arcgis-fileparser.js convert", 11 | "parse": "node ./terraformer-arcgis-fileparser.js parse" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git@github.com:Esri/terraformer-arcgis-parser.git" 16 | }, 17 | "keywords": [ 18 | "ArcGIS", 19 | "Esri", 20 | "GIS", 21 | "Geography" 22 | ], 23 | "author": "Graham Seyffert ", 24 | "license": "Apache-2.0", 25 | "dependencies": { 26 | "terraformer-arcgis-parser": "~1.0.5", 27 | "terraformer": "~1.0.7" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/terraformer-arcgis-fileparser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Copyright 2017 by Graham Freeman Seyffert 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | const fs = require('fs'); 18 | let ArcGIS; 19 | try { ArcGIS = require('terraformer-arcgis-parser'); } 20 | catch (err) { 21 | const message = 'Could not load Terraformer - please make sure ' 22 | + 'to run \'npm install\' before running.' 23 | return console.log(message, err); 24 | } 25 | 26 | function openFile(fileName) { 27 | return new Promise((resolve, reject) => { 28 | fs.open(fileName, 'r+', (err, fd) => { 29 | if (err) return reject(err); 30 | fs.fstat(fd, (err, stats) => { 31 | if (err) return reject(err); 32 | const fileData = { 33 | fd: fd, 34 | fileName: fileName, 35 | size: stats.size 36 | }; 37 | resolve(fileData); 38 | }); 39 | }); 40 | }); 41 | } 42 | 43 | function readFile(file) { 44 | const buffLen = file.size; 45 | const chunkSize = buffLen < 1024 ? buffLen 46 | : 1024; 47 | const buff = Buffer.alloc(buffLen); 48 | return tryRead(file.fd, buff, 0, chunkSize) 49 | .then((fileData) => { 50 | fileData.fileName = file.fileName; 51 | return fileData; 52 | }); 53 | } 54 | 55 | // Reads file by chunkSize at a time. 56 | // Will resolve with the buffer if it's full, otherwise will 57 | // resolve with nothing, indicating another read should happen. 58 | function tryRead(fd, buff, offset, length) { 59 | return new Promise((resolve, reject) => { 60 | fs.read(fd, buff, offset, length, null, (err, bytesRead, buffer) => { 61 | if (err) return reject(err); 62 | const buffLen = buffer.length; 63 | offset += bytesRead; 64 | if (offset >= buffLen) return resolve(buff); 65 | if ((offset + length) >= buffLen) length = buffLen - offset; 66 | return resolve(); 67 | }); 68 | }) 69 | .then( 70 | (fullBuffer) => { 71 | return fullBuffer ? { data: fullBuffer, fd: fd } 72 | : tryRead(fd, buff, offset, length); 73 | }, 74 | (err) => { throw err; } 75 | ); 76 | } 77 | 78 | function convertFile(fileData, operation) { 79 | const toConvert = JSON.parse(fileData.data.toString()); 80 | const newFileData = {}; 81 | let outputFileExtension; 82 | if (operation === 'convert') { 83 | outputFileExtension = '.json'; 84 | newFileData.features = ArcGIS.convert(toConvert); 85 | } else if (operation === 'parse') { 86 | outputFileExtension = '.geojson'; 87 | const firstFeature = toConvert.features[0]; 88 | const isGeoCollection = firstFeature.rings 89 | || firstFeature.paths 90 | || firstFeature.points 91 | || (firstFeature.x && firstFeature.y); 92 | const converted = toConvert.features.map(feature => ArcGIS.parse(feature)); 93 | if (isGeoCollection) { 94 | newFileData.type = 'GeometryCollection'; 95 | newFileData.geometries = converted; 96 | } else { 97 | newFileData.type = 'FeatureCollection'; 98 | newFileData.features = converted; 99 | } 100 | } 101 | // Considers file paths may be relative or not in CWD 102 | let fileNameSplit = fileData.fileName.split('.'); 103 | let fileNameWithoutExtension = fileNameSplit[fileNameSplit.length - 2]; 104 | fileNameSplit = fileNameWithoutExtension.split('/'); 105 | fileNameWithoutExtension = fileNameSplit[fileNameSplit.length - 1]; 106 | const outputFileName = fileNameWithoutExtension + outputFileExtension; 107 | const output = { 108 | data: JSON.stringify(newFileData, null, 2), 109 | fd: fileData.fd, 110 | fileName: outputFileName 111 | }; 112 | return output; 113 | } 114 | 115 | let operation = process.argv[2]; 116 | if (!operation) operation = 'convert'; 117 | 118 | const fileName = process.argv[3]; 119 | if (!fileName) { 120 | return console.log('Please specify a file to ' + operation + '.'); 121 | } 122 | 123 | // Kickoff 124 | openFile(fileName) 125 | .then(file => { return readFile(file); }) 126 | .then(fileData => { return convertFile(fileData, operation); }) 127 | .then(output => { 128 | fs.writeFile(output.fileName, output.data, (err) => { 129 | if (err) return console.log(err); 130 | fs.close(output.fd, (err) => { 131 | if (err) console.log(err); 132 | else console.log('Result written to ' + output.fileName); 133 | }); 134 | }); 135 | }) 136 | .catch(err => { return console.log(err); }); 137 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export interface SpatialReference { 4 | } 5 | 6 | export interface SpatialReferenceWkid extends SpatialReference { 7 | wkid?: number; 8 | latestWkid?: number; 9 | vcsWkid?: number; 10 | latestVcsWkid?: number; 11 | } 12 | 13 | export interface SpatialReferenceWkt extends SpatialReference { 14 | wkt?: string; 15 | latestWkt?: string; 16 | } 17 | 18 | export interface Geometry { 19 | spatialReference?: SpatialReference; 20 | } 21 | 22 | export interface HasZM { 23 | hasZ?: boolean; 24 | hasM?: boolean; 25 | } 26 | 27 | export interface Point extends Geometry { 28 | x: number; 29 | y: number; 30 | z?: number; 31 | m?: number; 32 | } 33 | 34 | export interface Polyline extends HasZM, Geometry { 35 | paths: number[][][]; 36 | } 37 | 38 | export interface Polygon extends HasZM, Geometry { 39 | rings: number[][][]; 40 | } 41 | 42 | export interface Multipoint extends HasZM, Geometry { 43 | points: number[][]; 44 | } 45 | 46 | export interface Envelope extends Geometry { 47 | xmin: number; 48 | xmax: number; 49 | ymin: number; 50 | ymax: number; 51 | 52 | zmin?: number; 53 | zmax?: number; 54 | 55 | mmin?: number; 56 | mmax?: number; 57 | } 58 | 59 | export interface Feature { 60 | geometry: Geometry; 61 | attributes: any; 62 | } 63 | 64 | export interface Field { 65 | name: string; 66 | type: string; 67 | alias?: string; 68 | length?: number; 69 | } 70 | 71 | export type esriGeometryType = "esriGeometryPoint" | "esriGeometryMultipoint" | "esriGeometryPolyline" | 72 | "esriGeometryPolygon" | "esriGeometryEnvelope"; 73 | 74 | export interface FeatureSet extends HasZM { 75 | objectIdFieldName?: string; // optional 76 | globalIdFieldName?: string; // optional 77 | displayFieldName?: string; // optional 78 | geometryType?: esriGeometryType; // for feature layers only 79 | spatialReference?: SpatialReference; // for feature layers only. 80 | fields?: Field[]; 81 | features: Feature[]; 82 | } 83 | 84 | export interface ParseOptions { 85 | sr?: number; 86 | idAttribute?: string; 87 | } 88 | export interface ConvertOptions { 89 | idAttribute?: string; 90 | } 91 | 92 | export function parse(json: T, options?: ParseOptions): GeoJSON.GeometryObject; 93 | export function parse(json: Feature, options?: ParseOptions): GeoJSON.Feature; 94 | 95 | export function convert(geoJSON: GeoJSON.FeatureCollection, options?: ConvertOptions): FeatureSet; 96 | export function convert(geoJSON: GeoJSON.Feature, options?: ConvertOptions): Feature; 97 | export function convert(geoJSON: T, options?: ConvertOptions): Geometry; 98 | -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": ["arcgisSpec.js"], 4 | "helpers": [], 5 | "stopSpecOnExpectationFailure": false, 6 | "random": false 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terraformer-arcgis-parser", 3 | "version": "1.1.0", 4 | "description": "ArcGIS JSON format parser and converter", 5 | "main": "terraformer-arcgis-parser.js", 6 | "files": [ 7 | "index.d.ts", 8 | "terraformer-arcgis-parser.min.js", 9 | "terraformer-arcgis-parser.d.ts" 10 | ], 11 | "directories": { 12 | "test": "test" 13 | }, 14 | "scripts": { 15 | "test": "npm run test:node && grunt test", 16 | "test:node": "jasmine --config=jasmine.json", 17 | "test:ts": "tsc" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git@github.com:Esri/terraformer-arcgis-parser.git" 22 | }, 23 | "keywords": [ 24 | "ArcGIS", 25 | "Esri", 26 | "GIS", 27 | "Geography" 28 | ], 29 | "author": "Patrick Arlt (http://patrickarlt.com)", 30 | "license": "MIT", 31 | "dependencies": { 32 | "@types/geojson": "^1.0.0", 33 | "terraformer": "~1.0.4" 34 | }, 35 | "devDependencies": { 36 | "benchmark": "~1.0.0", 37 | "gh-release": "^3.2.1", 38 | "grunt": "^1.0.3", 39 | "grunt-complexity": "~0.1.3", 40 | "grunt-contrib-jasmine": "^2.0.1", 41 | "grunt-contrib-uglify": "~2.0.0", 42 | "grunt-template-jasmine-istanbul": "~0.2.4", 43 | "jasmine": "^3.1.0", 44 | "tslint": "^4.5.1", 45 | "typescript": "^2.2.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # config 4 | VERSION=$(node --eval "console.log(require('./package.json').version);") 5 | NAME=$(node --eval "console.log(require('./package.json').name);") 6 | 7 | # build and test 8 | npm test || exit 1 9 | 10 | # checkout temp branch for release 11 | git checkout -b gh-release 12 | 13 | # create built library (and versioned copy) 14 | grunt uglify 15 | 16 | # force add files 17 | git add terraformer-arcgis-parser.min.js -f 18 | 19 | # commit changes with a versioned commit message 20 | git commit -m "build $VERSION" 21 | 22 | # push commit so it exists on GitHub when we run gh-release 23 | git push https://github.com/Esri/terraformer-arcgis-parser gh-release 24 | 25 | # create copy of minified file with version number appended 26 | cp terraformer-arcgis-parser.min.js $NAME-$VERSION.min.js 27 | 28 | # run gh-release to create the tag and push release to github 29 | gh-release --assets $NAME-$VERSION.min.js 30 | 31 | # remove copy after the asset is attached to the github release 32 | rm $NAME-$VERSION.min.js 33 | 34 | # checkout master and delete release branch locally and on GitHub 35 | git checkout master 36 | git branch -D gh-release 37 | git push https://github.com/Esri/terraformer-arcgis-parser :gh-release 38 | 39 | # publish release on NPM 40 | npm publish -------------------------------------------------------------------------------- /spec/arcgisSpec.js: -------------------------------------------------------------------------------- 1 | if(typeof module === "object"){ 2 | var Terraformer = require("terraformer"); 3 | Terraformer.ArcGIS = require("../terraformer-arcgis-parser.js"); 4 | } 5 | 6 | describe("ArcGIS Tools", function(){ 7 | 8 | it("should convert a GeoJSON Point to an ArcGIS Point", function() { 9 | var input = { 10 | "type": "Point", 11 | "coordinates": [-58.7109375,47.4609375] 12 | }; 13 | 14 | var output = Terraformer.ArcGIS.convert(input); 15 | 16 | expect(output).toEqual({ 17 | "x":-58.7109375, 18 | "y":47.4609375, 19 | "spatialReference":{ 20 | "wkid":4326 21 | } 22 | }); 23 | }); 24 | 25 | it("should convert a GeoJSON Point with Z to an ArcGIS Point with Z", function() { 26 | var input = { 27 | "type": "Point", 28 | "coordinates": [-58.7109375,47.4609375, 100] 29 | }; 30 | 31 | var output = Terraformer.ArcGIS.convert(input); 32 | 33 | expect(output).toEqual({ 34 | "x":-58.7109375, 35 | "y":47.4609375, 36 | "z": 100, 37 | "spatialReference":{ 38 | "wkid":4326 39 | } 40 | }); 41 | }); 42 | 43 | it("should convert a GeoJSON Point with Z and M to an ArcGIS Point with Z and M", function() { 44 | var input = { 45 | "type": "Point", 46 | "coordinates": [-58.7109375,47.4609375, 100, 50] 47 | }; 48 | 49 | var output = Terraformer.ArcGIS.convert(input); 50 | 51 | expect(output).toEqual({ 52 | "x":-58.7109375, 53 | "y":47.4609375, 54 | "z": 100, 55 | "m": 50, 56 | "spatialReference":{ 57 | "wkid":4326 58 | } 59 | }); 60 | }); 61 | 62 | it("should convert a GeoJSON Null Island to an ArcGIS Point", function() { 63 | var input = { 64 | "type": "Point", 65 | "coordinates": [0,0] 66 | }; 67 | 68 | var output = Terraformer.ArcGIS.convert(input); 69 | 70 | expect(output).toEqual({ 71 | "x":0, 72 | "y":0, 73 | "spatialReference":{ 74 | "wkid":4326 75 | } 76 | }); 77 | }); 78 | 79 | it("should convert a GeoJSON LineString to an ArcGIS Polyline", function() { 80 | var input = { 81 | "type": "LineString", 82 | "coordinates": [ [21.4453125,-14.0625],[33.3984375,-20.7421875],[38.3203125,-24.609375] ] 83 | }; 84 | 85 | var output = Terraformer.ArcGIS.convert(input); 86 | 87 | expect(output).toEqual({ 88 | "paths":[ 89 | [ [21.4453125,-14.0625],[33.3984375,-20.7421875],[38.3203125,-24.609375] ] 90 | ], 91 | "spatialReference":{ 92 | "wkid":4326 93 | } 94 | }); 95 | }); 96 | 97 | it("should convert a GeoJSON Polygon to an ArcGIS Polygon", function() { 98 | var input = { 99 | "type": "Polygon", 100 | "coordinates": [ 101 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 102 | ] 103 | }; 104 | 105 | var output = Terraformer.ArcGIS.convert(input); 106 | 107 | expect(output).toEqual({ 108 | "rings":[ 109 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 110 | ], 111 | "spatialReference":{ 112 | "wkid":4326 113 | } 114 | }); 115 | }); 116 | 117 | it("should convert a GeoJSON Polygon w/ a hole to an ArcGIS Polygon w/ 2 rings", function() { 118 | var input = { 119 | "type": "Polygon", 120 | "coordinates": [ 121 | [ [100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0] ], 122 | [ [100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2] ] 123 | ] 124 | }; 125 | 126 | var output = Terraformer.ArcGIS.convert(input); 127 | 128 | expect(output).toEqual({ 129 | "rings": [ 130 | [ [100, 0], [100, 1], [101, 1], [101, 0], [100, 0] ], 131 | [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ] 132 | ], 133 | "spatialReference":{ 134 | "wkid":4326 135 | } 136 | }); 137 | }); 138 | 139 | it("should strip invalid rings when converting a GeoJSON Polygon to and ArcGIS Polygon", function() { 140 | var input = { 141 | "type": "Polygon", 142 | "coordinates": [ 143 | [ [100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0] ], 144 | [ [100.2,0.2],[100.8,0.2],[100.2,0.2] ] 145 | ] 146 | }; 147 | 148 | var output = Terraformer.ArcGIS.convert(input); 149 | 150 | expect(output).toEqual({ 151 | "rings": [ 152 | [ [100, 0], [100, 1], [101, 1], [101, 0], [100, 0] ] 153 | ], 154 | "spatialReference":{ 155 | "wkid":4326 156 | } 157 | }); 158 | }); 159 | 160 | it("should close ring when converting a GeoJSON Polygon w/ a hole to an ArcGIS Polygon", function() { 161 | var input = { 162 | "type": "Polygon", 163 | "coordinates": [ 164 | [ [100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0] ], 165 | [ [100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8] ] 166 | ] 167 | }; 168 | 169 | var output = Terraformer.ArcGIS.convert(input); 170 | 171 | expect(output).toEqual({ 172 | "rings": [ 173 | [ [100, 0], [100, 1], [101, 1], [101, 0], [100, 0] ], 174 | [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ] 175 | ], 176 | "spatialReference":{ 177 | "wkid":4326 178 | } 179 | }); 180 | }); 181 | 182 | it("should convert a GeoJSON MultiPoint to an ArcGIS Multipoint", function() { 183 | var input = { 184 | "type": "MultiPoint", 185 | "coordinates": [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625] ] 186 | }; 187 | 188 | var output = Terraformer.ArcGIS.convert(input); 189 | 190 | expect(output).toEqual({ 191 | "points":[ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625] ], 192 | "spatialReference":{ 193 | "wkid":4326 194 | } 195 | }); 196 | }); 197 | 198 | it("should convert a GeoJSON MultiLineString to an ArcGIS Polyline", function() { 199 | var input = { 200 | "type": "MultiLineString", 201 | "coordinates": [ 202 | [ [41.8359375,71.015625],[56.953125,33.75] ], 203 | [ [21.796875,36.5625],[47.8359375,71.015625] ] 204 | ] 205 | }; 206 | 207 | var output = Terraformer.ArcGIS.convert(input); 208 | 209 | expect(output).toEqual({ 210 | "paths":[ 211 | [ [41.8359375,71.015625],[56.953125,33.75] ], 212 | [ [21.796875,36.5625],[47.8359375,71.015625] ] 213 | ], 214 | "spatialReference":{ 215 | "wkid":4326 216 | } 217 | }); 218 | }); 219 | 220 | it("should convert a GeoJSON MultiPolygon to an ArcGIS Polygon", function() { 221 | var input = { 222 | "type": "MultiPolygon", 223 | "coordinates": [ 224 | [ 225 | [ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ] 226 | ], 227 | [ 228 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] 229 | ] 230 | ] 231 | }; 232 | 233 | var output = Terraformer.ArcGIS.convert(input); 234 | expect(output).toEqual({ 235 | "rings":[ 236 | [ [102, 2], [102, 3], [103, 3], [103, 2], [102, 2] ], 237 | [ [100, 0], [100, 1], [101, 1], [101, 0], [100, 0] ] 238 | ], 239 | "spatialReference": { 240 | "wkid":4326 241 | } 242 | }); 243 | }); 244 | 245 | it("should convert a GeoJSON MultiPolygon w/ holes to an ArcGIS Polygon", function() { 246 | var input = { 247 | "type": "MultiPolygon", 248 | "coordinates": [ 249 | [ 250 | [ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ] 251 | ], 252 | [ 253 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ], 254 | [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ] 255 | ] 256 | ] 257 | }; 258 | 259 | var output = Terraformer.ArcGIS.convert(input); 260 | expect(output).toEqual({ 261 | "spatialReference": { 262 | "wkid": 4326 263 | }, 264 | "rings": [ 265 | [ [102,2],[102,3],[103,3],[103,2],[102,2] ], 266 | [ [100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2] ], 267 | [ [100,0],[100,1],[101,1],[101,0],[100,0] ] 268 | ] 269 | }); 270 | }); 271 | 272 | it("should close rings when converting a GeoJSON MultiPolygon w/ holes to an ArcGIS Polygon", function() { 273 | var input = { 274 | "type": "MultiPolygon", 275 | "coordinates": [ 276 | [ 277 | [ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0] ] 278 | ], 279 | [ 280 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0] ], 281 | [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8] ] 282 | ] 283 | ] 284 | }; 285 | 286 | var output = Terraformer.ArcGIS.convert(input); 287 | expect(output).toEqual({ 288 | "spatialReference": { 289 | "wkid": 4326 290 | }, 291 | "rings": [ 292 | [ [102,2],[102,3],[103,3],[103,2],[102,2] ], 293 | [ [100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2] ], 294 | [ [100,0],[100,1],[101,1],[101,0],[100,0] ] 295 | ] 296 | }); 297 | }); 298 | 299 | it('should still parse holes outside the outer rings', function(){ 300 | var input = { 301 | "rings": [ 302 | [ [-122.45,45.63], [-122.45,45.68], [-122.39,45.68], [-122.39,45.63], [-122.45,45.63] ], 303 | [ [-122.46,45.64], [-122.4,45.64], [-122.4,45.66], [-122.46,45.66], [-122.46,45.64] ] 304 | ] 305 | } 306 | 307 | var output = Terraformer.ArcGIS.parse(input); 308 | 309 | var expected = [ 310 | [ [-122.45,45.63], [-122.39,45.63], [-122.39,45.68], [-122.45,45.68], [-122.45,45.63] ], 311 | [ [-122.46,45.64], [-122.46,45.66], [-122.4,45.66], [-122.4,45.64], [-122.46,45.64] ] 312 | ]; 313 | 314 | expect(output.coordinates).toEqual(expected); 315 | }); 316 | 317 | it("should convert a GeoJSON Feature into an ArcGIS Feature", function(){ 318 | var input = { 319 | "type":"Feature", 320 | "id": "foo", 321 | "geometry": { 322 | "type": "Polygon", 323 | "coordinates": [ 324 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 325 | ] 326 | }, 327 | "properties": { 328 | "foo":"bar" 329 | } 330 | }; 331 | 332 | var output = Terraformer.ArcGIS.convert(input); 333 | 334 | expect(output).toEqual({ 335 | "geometry":{ 336 | "rings":[ 337 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 338 | ], 339 | "spatialReference":{ 340 | "wkid":4326 341 | } 342 | }, 343 | "attributes": { 344 | "foo":"bar", 345 | "OBJECTID": "foo" 346 | } 347 | }); 348 | }); 349 | 350 | it("should convert a GeoJSON Feature into an ArcGIS Feature w/ a custom id", function(){ 351 | var input = { 352 | "type":"Feature", 353 | "id": "foo", 354 | "geometry": { 355 | "type": "Polygon", 356 | "coordinates": [ 357 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 358 | ] 359 | }, 360 | "properties": { 361 | "foo":"bar" 362 | } 363 | }; 364 | 365 | var output = Terraformer.ArcGIS.convert(input, { 366 | idAttribute: "myId" 367 | }); 368 | 369 | expect(output).toEqual({ 370 | "geometry":{ 371 | "rings":[ 372 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 373 | ], 374 | "spatialReference":{ 375 | "wkid":4326 376 | } 377 | }, 378 | "attributes": { 379 | "foo":"bar", 380 | "myId": "foo" 381 | } 382 | }); 383 | }); 384 | 385 | it("should allow converting a GeoJSON Feature to an ArcGIS Feature with no properties or geometry", function(){ 386 | var input = { 387 | "type":"Feature", 388 | "id": "foo", 389 | "geometry": null, 390 | "properties": null 391 | }; 392 | 393 | var output = Terraformer.ArcGIS.convert(input); 394 | 395 | expect(output).toEqual({ 396 | "attributes": { 397 | "OBJECTID": "foo" 398 | } 399 | }); 400 | }); 401 | 402 | it("should convert a GeoJSON FeatureCollection into an array of ArcGIS Feature JSON", function(){ 403 | var input = { 404 | "type": "FeatureCollection", 405 | "features": [{ 406 | "type": "Feature", 407 | "geometry": { 408 | "type": "Point", 409 | "coordinates": [102.0, 0.5] 410 | }, 411 | "properties": { 412 | "prop0": "value0" 413 | } 414 | }, { 415 | "type": "Feature", 416 | "geometry": { 417 | "type": "LineString", 418 | "coordinates": [ 419 | [102.0, 0.0],[103.0, 1.0],[104.0, 0.0],[105.0, 1.0] 420 | ] 421 | }, 422 | "properties": { 423 | "prop0": "value0" 424 | } 425 | }, { 426 | "type": "Feature", 427 | "geometry": { 428 | "type": "Polygon", 429 | "coordinates": [ 430 | [ [100.0, 0.0],[101.0, 0.0],[101.0, 1.0],[100.0, 1.0],[100.0, 0.0] ] 431 | ] 432 | }, 433 | "properties": { 434 | "prop0": "value0" 435 | } 436 | }] 437 | }; 438 | 439 | var output = Terraformer.ArcGIS.convert(input); 440 | 441 | expect(output).toEqual([{ 442 | "geometry": { 443 | "x": 102, 444 | "y": 0.5, 445 | "spatialReference": { 446 | "wkid": 4326 447 | } 448 | }, 449 | "attributes": { 450 | "prop0": "value0" 451 | } 452 | }, { 453 | "geometry": { 454 | "paths": [ 455 | [[102, 0],[103, 1],[104, 0],[105, 1]] 456 | ], 457 | "spatialReference": { 458 | "wkid": 4326 459 | } 460 | }, 461 | "attributes": { 462 | "prop0": "value0" 463 | } 464 | }, { 465 | "geometry": { 466 | "rings": [ 467 | [ [100,0],[100,1],[101,1],[101,0],[100,0] ] 468 | ], 469 | "spatialReference": { 470 | "wkid": 4326 471 | } 472 | }, 473 | "attributes": { 474 | "prop0": "value0" 475 | } 476 | }]); 477 | }); 478 | 479 | it("should convert a GeoJSON GeometryCollection into an array of ArcGIS Geometries", function(){ 480 | var input = { 481 | "type" : "GeometryCollection", 482 | "geometries" : [{ 483 | "type" : "Polygon", 484 | "coordinates" : [[[-95, 43], [-95, 50], [-90, 50], [-91, 42], [-95, 43]]] 485 | }, { 486 | "type" : "LineString", 487 | "coordinates" : [[-89, 42], [-89, 50], [-80, 50], [-80, 42]] 488 | }, { 489 | "type" : "Point", 490 | "coordinates" : [-94, 46] 491 | }] 492 | }; 493 | 494 | var output = Terraformer.ArcGIS.convert(input); 495 | 496 | expect(output).toEqual([{ 497 | "rings": [ 498 | [[-95, 43],[-95, 50],[-90, 50],[-91, 42],[-95, 43]] 499 | ], 500 | "spatialReference": { 501 | "wkid": 4326 502 | } 503 | }, { 504 | "paths": [ 505 | [[-89, 42],[-89, 50],[-80, 50],[-80, 42]] 506 | ], 507 | "spatialReference": { 508 | "wkid": 4326 509 | } 510 | }, { 511 | "x": -94, 512 | "y": 46, 513 | "spatialReference": { 514 | "wkid": 4326 515 | } 516 | }]); 517 | }); 518 | 519 | it("should not modify the original GeoJSON object", function(){ 520 | var primitive = new Terraformer.FeatureCollection({ 521 | "type": "FeatureCollection", 522 | "features": [{ 523 | "type": "Feature", 524 | "geometry": { 525 | "type": "Point", 526 | "coordinates": [102.0, 0.5] 527 | }, 528 | "properties": { 529 | "prop0": "value0" 530 | } 531 | }, { 532 | "type": "Feature", 533 | "geometry": { 534 | "type": "LineString", 535 | "coordinates": [ 536 | [102.0, 0.0],[103.0, 1.0],[104.0, 0.0],[105.0, 1.0] 537 | ] 538 | }, 539 | "properties": { 540 | "prop0": "value0" 541 | } 542 | }, { 543 | "type": "Feature", 544 | "geometry": { 545 | "type": "Polygon", 546 | "coordinates": [ 547 | [ [100.0, 0.0],[101.0, 0.0],[101.0, 1.0],[100.0, 1.0],[100.0, 0.0] ] 548 | ] 549 | }, 550 | "properties": { 551 | "prop0": "value0" 552 | } 553 | }] 554 | }); 555 | 556 | var original = JSON.stringify(primitive); 557 | 558 | Terraformer.ArcGIS.convert(primitive); 559 | 560 | expect(original).toEqual(JSON.stringify(primitive)); 561 | }); 562 | 563 | it("if the GeoJSON includes a custom crs, output spatial reference should not be set", function() { 564 | var input = { 565 | "type": "Point", 566 | "coordinates": [123,456], 567 | "crs": { 568 | "type": "name", 569 | "properties": { 570 | "name": "urn:ogc:def:crs:EPSG::2913" 571 | } 572 | } 573 | }; 574 | 575 | var output = Terraformer.ArcGIS.convert(input); 576 | 577 | expect(output).toEqual({ 578 | "x": 123, 579 | "y": 456, 580 | "spatialReference": null 581 | }); 582 | }); 583 | 584 | it("if the GeoJSON includes a custom linked crs, output spatial reference should not be set", function() { 585 | var input = { 586 | "type": "Point", 587 | "coordinates": [123,456], 588 | "crs": { 589 | "type": "link", 590 | "properties": { 591 | "href": "http://spatialreference.org/ref/sr-org/7/ogcwkt/", 592 | "type": "ogcwkt" 593 | } 594 | }, 595 | }; 596 | 597 | var output = Terraformer.ArcGIS.convert(input); 598 | expect(output).toEqual({ 599 | "x": 123, 600 | "y": 456, 601 | "spatialReference": null 602 | }); 603 | }); 604 | 605 | it("should parse an ArcGIS Point in a Terraformer GeoJSON Point", function() { 606 | var input = { 607 | "x": -66.796875, 608 | "y": 20.0390625, 609 | "spatialReference": { 610 | "wkid": 4326 611 | } 612 | }; 613 | 614 | var output = Terraformer.ArcGIS.parse(input); 615 | 616 | expect(output.coordinates).toEqual([-66.796875,20.0390625]); 617 | expect(output instanceof Terraformer.Point).toBeTruthy(); 618 | }); 619 | 620 | it("should parse an ArcGIS Point with Z in a Terraformer GeoJSON Point with Z", function() { 621 | var input = { 622 | "x": -66.796875, 623 | "y": 20.0390625, 624 | "z": 100, 625 | "spatialReference": { 626 | "wkid": 4326 627 | } 628 | }; 629 | 630 | var output = Terraformer.ArcGIS.parse(input); 631 | 632 | expect(output.coordinates).toEqual([-66.796875,20.0390625, 100]); 633 | expect(output instanceof Terraformer.Point).toBeTruthy(); 634 | }); 635 | 636 | it("should parse an ArcGIS Point with Z and M in a Terraformer GeoJSON Point with Z and M", function() { 637 | var input = { 638 | "x": -66.796875, 639 | "y": 20.0390625, 640 | "z": 100, 641 | "m": 50, 642 | "spatialReference": { 643 | "wkid": 4326 644 | } 645 | }; 646 | 647 | var output = Terraformer.ArcGIS.parse(input); 648 | 649 | expect(output.coordinates).toEqual([-66.796875,20.0390625, 100, 50]); 650 | expect(output instanceof Terraformer.Point).toBeTruthy(); 651 | }); 652 | 653 | it("should parse an ArcGIS Point with M in a Terraformer GeoJSON Point with M", function() { 654 | var input = { 655 | "x": -66.796875, 656 | "y": 20.0390625, 657 | "m": 50, 658 | "spatialReference": { 659 | "wkid": 4326 660 | } 661 | }; 662 | 663 | var output = Terraformer.ArcGIS.parse(input); 664 | 665 | expect(output.coordinates).toEqual([-66.796875,20.0390625, undefined, 50]); 666 | expect(output instanceof Terraformer.Point).toBeTruthy(); 667 | }); 668 | 669 | it("should parse an ArcGIS Null Island in a Terraformer GeoJSON Point", function() { 670 | var input = { 671 | "x": 0, 672 | "y": 0, 673 | "spatialReference": { 674 | "wkid": 4326 675 | } 676 | }; 677 | 678 | var output = Terraformer.ArcGIS.parse(input); 679 | 680 | expect(output.coordinates).toEqual([0,0]); 681 | expect(output instanceof Terraformer.Point).toBeTruthy(); 682 | }); 683 | 684 | it("should parse an ArcGIS Polyline in a Terraformer GeoJSON LineString", function() { 685 | var input = { 686 | "paths": [ 687 | [ [6.6796875,47.8125],[-65.390625,52.3828125],[-52.3828125,42.5390625] ] 688 | ], 689 | "spatialReference": { 690 | "wkid": 4326 691 | } 692 | }; 693 | 694 | var output = Terraformer.ArcGIS.parse(input); 695 | 696 | expect(output.coordinates).toEqual([ [6.6796875,47.8125],[-65.390625,52.3828125],[-52.3828125,42.5390625] ]); 697 | expect(output instanceof Terraformer.LineString).toBeTruthy(); 698 | }); 699 | 700 | it("should parse an ArcGIS Polygon in a Terraformer GeoJSON Polygon", function() { 701 | var input = { 702 | "rings": [ 703 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 704 | ], 705 | "spatialReference": { 706 | "wkid": 4326 707 | } 708 | }; 709 | 710 | var output = Terraformer.ArcGIS.parse(input); 711 | 712 | expect(output.coordinates).toEqual([ [ [41.8359375,71.015625],[21.796875,36.5625],[56.953125,33.75],[41.8359375,71.015625] ] ]); 713 | expect(output.type).toEqual("Polygon"); 714 | }); 715 | 716 | it("should close rings when parsing an ArcGIS Polygon in a Terraformer GeoJSON Polygon", function() { 717 | var input = { 718 | "rings": [ 719 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625]] 720 | ], 721 | "spatialReference": { 722 | "wkid": 4326 723 | } 724 | }; 725 | 726 | var output = Terraformer.ArcGIS.parse(input); 727 | 728 | expect(output.coordinates).toEqual([ [ [41.8359375,71.015625],[21.796875,36.5625],[56.953125,33.75],[41.8359375,71.015625] ] ]); 729 | expect(output.type).toEqual("Polygon"); 730 | }); 731 | 732 | it("should parse an ArcGIS Multipoint in a Terraformer GeoJSON MultiPoint", function() { 733 | var input = { 734 | "points":[ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625] ], 735 | "spatialReference":{ 736 | "wkid":4326 737 | } 738 | }; 739 | 740 | var output = Terraformer.ArcGIS.parse(input); 741 | 742 | expect(output.coordinates).toEqual([ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625] ]); 743 | expect(output instanceof Terraformer.MultiPoint).toBeTruthy(); 744 | }); 745 | 746 | it("should parse an ArcGIS Polyline in a Terraformer GeoJSON MultiLineString", function() { 747 | var input = { 748 | "paths":[ 749 | [ [41.8359375,71.015625],[56.953125,33.75] ], 750 | [ [21.796875,36.5625],[41.8359375,71.015625] ] 751 | ], 752 | "spatialReference":{ 753 | "wkid":4326 754 | } 755 | }; 756 | 757 | var output = Terraformer.ArcGIS.parse(input); 758 | 759 | expect(output.coordinates).toEqual([[ [41.8359375,71.015625],[56.953125,33.75] ], [ [21.796875,36.5625],[41.8359375,71.015625] ]]); 760 | expect(output instanceof Terraformer.MultiLineString).toBeTruthy(); 761 | }); 762 | 763 | it("should parse an ArcGIS Polygon in a Terraformer GeoJSON MultiPolygon", function() { 764 | var input = { 765 | "rings":[ 766 | [[-122.63,45.52],[-122.57,45.53],[-122.52,45.50],[-122.49,45.48],[-122.64,45.49],[-122.63,45.52],[-122.63,45.52]], 767 | [[-83,35],[-74,35],[-74,41],[-83,41],[-83,35]] 768 | ], 769 | "spatialReference": { 770 | "wkid":4326 771 | } 772 | }; 773 | 774 | var output = Terraformer.ArcGIS.parse(input); 775 | 776 | expect(output.coordinates).toEqual([ 777 | [ 778 | [ [-122.63,45.52],[-122.63,45.52],[-122.64,45.49],[-122.49,45.48],[-122.52,45.5],[-122.57,45.53],[-122.63,45.52] ] 779 | ], 780 | [ 781 | [ [-83,35],[-74,35],[-74,41],[-83,41],[-83,35] ] 782 | ] 783 | ]); 784 | expect(output.type).toEqual("MultiPolygon"); 785 | }); 786 | 787 | it("should strip invalid rings when converting ArcGIS Polygons to GeoJSON", function() { 788 | var input = { 789 | "rings":[ 790 | [[-122.63,45.52],[-122.57,45.53],[-122.52,45.50],[-122.49,45.48],[-122.64,45.49],[-122.63,45.52],[-122.63,45.52]], 791 | [[-83,35],[-74,35],[-83,35]] // closed but too small 792 | ], 793 | "spatialReference": { 794 | "wkid":4326 795 | } 796 | }; 797 | 798 | var output = Terraformer.ArcGIS.parse(input); 799 | 800 | expect(output.coordinates).toEqual([ 801 | [ [-122.63, 45.52],[-122.63, 45.52],[-122.64, 45.49],[-122.49, 45.48],[-122.52, 45.5],[-122.57, 45.53],[-122.63, 45.52] ] 802 | ]); 803 | expect(output.type).toEqual("Polygon"); 804 | }); 805 | 806 | it("should properly close rings when converting an ArcGIS Polygon in a Terraformer GeoJSON MultiPolygon", function() { 807 | var input = { 808 | "rings":[ 809 | [[-122.63,45.52],[-122.57,45.53],[-122.52,45.50],[-122.49,45.48],[-122.64,45.49]], 810 | [[-83,35],[-74,35],[-74,41],[-83,41]] 811 | ], 812 | "spatialReference": { 813 | "wkid":4326 814 | } 815 | }; 816 | 817 | var output = Terraformer.ArcGIS.parse(input); 818 | 819 | expect(output.coordinates).toEqual([ 820 | [ 821 | [ [-122.63, 45.52],[-122.64, 45.49],[-122.49, 45.48],[-122.52, 45.5],[-122.57, 45.53],[-122.63, 45.52] ] 822 | ], 823 | [ 824 | [ [-83,35],[-74,35],[-74,41],[-83,41],[-83,35] ] 825 | ] 826 | ]); 827 | expect(output.type).toEqual("MultiPolygon"); 828 | }); 829 | 830 | it("should parse an ArcGIS MultiPolygon with holes in web mercator to a GeoJSON MultiPolygon", function(){ 831 | var input = { 832 | "type":"polygon", 833 | "rings":[ 834 | [ [-100.74462180954974,39.95017165502381],[-94.50439384003792,39.91647453608879],[-94.41650267263967,34.89313438177965],[-100.78856739324887,34.85708140996771],[-100.74462180954974,39.95017165502381] ], 835 | [ [-99.68993678392353,39.341088433448896],[-99.68993678392353,38.24507658785885],[-98.67919734199646,37.86444431771113],[-98.06395917020868,38.210554846669694],[-98.06395917020868,39.341088433448896],[-99.68993678392353,39.341088433448896] ], 836 | [ [-96.83349180978595,37.23732027507514],[-97.31689323047635,35.967330282988534],[-96.5698183075912,35.57512048069255],[-95.42724211456674,36.357601429255965],[-96.83349180978595,37.23732027507514] ], 837 | [ [-101.4916967324349,38.24507658785885],[-101.44775114873578,36.073960493943744],[-103.95263145328033,36.03843312329154],[-103.68895795108557,38.03770050767439],[-101.4916967324349,38.24507658785885] ] 838 | ], 839 | "spatialReference":{ 840 | "wkid":4326 841 | } 842 | }; 843 | var output = Terraformer.ArcGIS.parse(input); 844 | 845 | expect(output.coordinates).toEqual([ 846 | [ 847 | [ [-100.74462180954974, 39.95017165502381],[-100.78856739324887, 34.85708140996771],[-94.41650267263967, 34.89313438177965],[-94.50439384003792, 39.91647453608879],[-100.74462180954974, 39.95017165502381] ], 848 | [ [-96.83349180978595, 37.23732027507514],[-95.42724211456674, 36.357601429255965],[-96.5698183075912, 35.57512048069255],[-97.31689323047635, 35.967330282988534],[-96.83349180978595, 37.23732027507514] ], 849 | [ [-99.68993678392353, 39.341088433448896],[-98.06395917020868, 39.341088433448896],[-98.06395917020868, 38.210554846669694],[-98.67919734199646, 37.86444431771113],[-99.68993678392353, 38.24507658785885],[-99.68993678392353, 39.341088433448896] ] 850 | ], 851 | [ 852 | [ [-101.4916967324349, 38.24507658785885], [-103.68895795108557, 38.03770050767439], [-103.95263145328033, 36.03843312329154], [-101.44775114873578, 36.073960493943744], [-101.4916967324349, 38.24507658785885] ] 853 | ] 854 | ]); 855 | expect(output.type).toEqual("MultiPolygon"); 856 | }); 857 | 858 | it("should parse an ArcGIS Feature into a Terraformer Feature", function(){ 859 | var input = { 860 | "geometry": { 861 | "rings": [ 862 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 863 | ], 864 | "spatialReference": { 865 | "wkid": 4326 866 | } 867 | }, 868 | "attributes": { 869 | "foo": "bar" 870 | } 871 | }; 872 | 873 | var output = Terraformer.ArcGIS.parse(input); 874 | 875 | expect(output.geometry.coordinates).toEqual([ 876 | [ [41.8359375,71.015625],[21.796875,36.5625],[56.953125, 33.75],[41.8359375,71.015625] ] 877 | ]); 878 | expect(output.geometry.type).toEqual("Polygon"); 879 | expect(output instanceof Terraformer.Feature).toBeTruthy(); 880 | }); 881 | 882 | it("should parse an ArcGIS Feature w/ OBJECTID into a Terraformer Feature", function(){ 883 | var input = { 884 | "geometry": { 885 | "rings": [ 886 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 887 | ], 888 | "spatialReference": { 889 | "wkid": 4326 890 | } 891 | }, 892 | "attributes": { 893 | "OBJECTID": 123 894 | } 895 | }; 896 | 897 | var output = Terraformer.ArcGIS.parse(input); 898 | 899 | expect(output.id).toEqual(123); 900 | }); 901 | 902 | it("should parse an ArcGIS Feature w/ FID into a Terraformer Feature", function(){ 903 | var input = { 904 | "geometry": { 905 | "rings": [ 906 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 907 | ], 908 | "spatialReference": { 909 | "wkid": 4326 910 | } 911 | }, 912 | "attributes": { 913 | "FID": 123 914 | } 915 | }; 916 | 917 | var output = Terraformer.ArcGIS.parse(input); 918 | 919 | expect(output.id).toEqual(123); 920 | }); 921 | 922 | it("should parse an ArcGIS Feature w/ a custom id into a Terraformer Feature", function(){ 923 | var input = { 924 | "geometry": { 925 | "rings": [ 926 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 927 | ], 928 | "spatialReference": { 929 | "wkid": 4326 930 | } 931 | }, 932 | "attributes": { 933 | "FooId": 123 934 | } 935 | }; 936 | 937 | var output = Terraformer.ArcGIS.parse(input, { 938 | idAttribute: "FooId" 939 | }); 940 | 941 | expect(output.id).toEqual(123); 942 | }); 943 | 944 | it("should parse an ArcGIS Feature w/ empty attributes into a Terraformer Feature", function(){ 945 | var input = { 946 | "geometry": { 947 | "rings": [ 948 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 949 | ], 950 | "spatialReference": { 951 | "wkid": 4326 952 | } 953 | }, 954 | "attributes": {} 955 | }; 956 | 957 | var output = Terraformer.ArcGIS.parse(input); 958 | 959 | expect(output.geometry.coordinates).toEqual([ 960 | [ [41.8359375,71.015625],[21.796875,36.5625],[56.953125,33.75],[41.8359375,71.015625] ] 961 | ]); 962 | expect(output.geometry.type).toEqual("Polygon"); 963 | expect(output instanceof Terraformer.Feature).toBeTruthy(); 964 | }); 965 | 966 | it("should parse an ArcGIS Feature w/ no attributes into a Terraformer Feature", function(){ 967 | var input = { 968 | "geometry": { 969 | "rings": [ 970 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 971 | ], 972 | "spatialReference": { 973 | "wkid": 4326 974 | } 975 | } 976 | }; 977 | 978 | var output = Terraformer.ArcGIS.parse(input); 979 | 980 | expect(output.geometry.coordinates).toEqual([ 981 | [ [41.8359375,71.015625],[21.796875,36.5625],[56.953125,33.75],[41.8359375,71.015625] ] 982 | ]); 983 | expect(output.geometry.type).toEqual("Polygon"); 984 | expect(output instanceof Terraformer.Feature).toBeTruthy(); 985 | expect(output.properties).toEqual(null); 986 | }); 987 | 988 | it("should parse an ArcGIS Feature w/ no geometry into a Terraformer Feature", function(){ 989 | var input = { 990 | "attributes": { 991 | "foo": "bar" 992 | } 993 | }; 994 | 995 | var output = Terraformer.ArcGIS.parse(input); 996 | expect(output.geometry).toEqual(null); 997 | expect(output instanceof Terraformer.Feature).toBeTruthy(); 998 | expect(output.properties.foo).toEqual("bar"); 999 | }); 1000 | 1001 | it("should not reproject to WGS84/4326 while parsing", function(){ 1002 | var input = { 1003 | "x": -13580977.876779145, 1004 | "y": 5621521.486191948, 1005 | "spatialReference": { 1006 | "wkid": 3857 1007 | } 1008 | }; 1009 | 1010 | var output = Terraformer.ArcGIS.parse(input); 1011 | expect(output.coordinates).toEqual([-13580977.876779145, 5621521.486191948]); 1012 | }); 1013 | 1014 | it("should not modify the original ArcGIS Geometry", function(){ 1015 | var input = { 1016 | "geometry": { 1017 | "rings": [ 1018 | [ [41.8359375,71.015625],[56.953125,33.75],[21.796875,36.5625],[41.8359375,71.015625] ] 1019 | ], 1020 | "spatialReference": { 1021 | "wkid": 4326 1022 | } 1023 | }, 1024 | "attributes": { 1025 | "foo": "bar" 1026 | } 1027 | }; 1028 | 1029 | var original = JSON.stringify(input); 1030 | 1031 | Terraformer.ArcGIS.parse(input); 1032 | 1033 | expect(original).toEqual(JSON.stringify(input)); 1034 | }); 1035 | 1036 | it("should decompress ArcGIS compressed geometry features into GeoJSON Features", function(){ 1037 | var input = { 1038 | "compressedGeometry": "+1m91-66os4+1poms+1+91+3+3j" 1039 | }; 1040 | 1041 | var output = Terraformer.ArcGIS.parse(input); 1042 | 1043 | expect(output.type).toEqual("Feature"); 1044 | expect(output.geometry.type).toEqual("LineString"); 1045 | expect(output.geometry.coordinates).toEqual([ [ -117.1816137447153, 34.057461545380946 ],[ -117.18159575425025, 34.06266078978142 ], [ -117.18154178285509, 34.06472969326257 ] ]); 1046 | }); 1047 | 1048 | it("should decompress ArcGIS compressed geometries into Polylines", function(){ 1049 | var output = Terraformer.ArcGIS.parseCompressedGeometry("+1m91-66os4+1poms+1+91+3+3j"); 1050 | 1051 | expect(output.type).toEqual("LineString"); 1052 | expect(output.coordinates).toEqual([ [ -117.1816137447153, 34.057461545380946 ],[ -117.18159575425025, 34.06266078978142 ], [ -117.18154178285509, 34.06472969326257 ] ]); 1053 | }); 1054 | 1055 | it("should parse an ArcGIS Extent into a Terraformer GeoJSON Polygon", function () { 1056 | var input = { 1057 | "xmax": -35.5078125, 1058 | "ymax": 41.244772343082076, 1059 | "xmin": -13.7109375, 1060 | "ymin": 54.36775852406841, 1061 | "spatialReference": { 1062 | "wkid": 4326 1063 | } 1064 | }; 1065 | 1066 | var output = Terraformer.ArcGIS.parse(input); 1067 | 1068 | expect(output.coordinates).toEqual([[[-35.5078125, 41.244772343082076], [-13.7109375, 41.244772343082076], [-13.7109375, 54.36775852406841], [-35.5078125, 54.36775852406841], [-35.5078125, 41.244772343082076]]]); 1069 | expect(output.type).toEqual("Polygon"); 1070 | }); 1071 | 1072 | }); 1073 | -------------------------------------------------------------------------------- /terraformer-arcgis-parser.js: -------------------------------------------------------------------------------- 1 | /* globals Terraformer */ 2 | (function (root, factory) { 3 | 4 | // Node. 5 | if(typeof module === 'object' && typeof module.exports === 'object') { 6 | exports = module.exports = factory(require('terraformer')); 7 | } 8 | 9 | // Browser Global. 10 | if(typeof root.navigator === "object") { 11 | if (!root.Terraformer){ 12 | throw new Error("Terraformer.ArcGIS requires the core Terraformer library. https://github.com/esri/Terraformer"); 13 | } 14 | root.Terraformer.ArcGIS = factory(root.Terraformer); 15 | } 16 | 17 | }(this, function(Terraformer) { 18 | var exports = {}; 19 | 20 | // https://github.com/Esri/terraformer-arcgis-parser/issues/10 21 | function decompressGeometry(str) { 22 | var xDiffPrev = 0; 23 | var yDiffPrev = 0; 24 | var points = []; 25 | var x, y; 26 | var strings; 27 | var coefficient; 28 | 29 | // Split the string into an array on the + and - characters 30 | strings = str.match(/((\+|\-)[^\+\-]+)/g); 31 | 32 | // The first value is the coefficient in base 32 33 | coefficient = parseInt(strings[0], 32); 34 | 35 | for (var j = 1; j < strings.length; j += 2) { 36 | // j is the offset for the x value 37 | // Convert the value from base 32 and add the previous x value 38 | x = (parseInt(strings[j], 32) + xDiffPrev); 39 | xDiffPrev = x; 40 | 41 | // j+1 is the offset for the y value 42 | // Convert the value from base 32 and add the previous y value 43 | y = (parseInt(strings[j + 1], 32) + yDiffPrev); 44 | yDiffPrev = y; 45 | 46 | points.push([x / coefficient, y / coefficient]); 47 | } 48 | 49 | return points; 50 | } 51 | 52 | // checks if the first and last points of a ring are equal and closes the ring 53 | function closeRing(coordinates) { 54 | if (!pointsEqual(coordinates[0], coordinates[coordinates.length - 1])) { 55 | coordinates.push(coordinates[0]); 56 | } 57 | return coordinates; 58 | } 59 | 60 | // checks if 2 x,y points are equal 61 | function pointsEqual(a, b) { 62 | for (var i = 0; i < a.length; i++) { 63 | if (a[i] !== b[i]) { 64 | return false; 65 | } 66 | } 67 | return true; 68 | } 69 | 70 | // shallow object clone for feature properties and attributes 71 | // from http://jsperf.com/cloning-an-object/2 72 | function clone(obj) { 73 | var target = {}; 74 | for (var i in obj) { 75 | if (obj.hasOwnProperty(i)) { 76 | target[i] = obj[i]; 77 | } 78 | } 79 | return target; 80 | } 81 | 82 | // determine if polygon ring coordinates are clockwise. clockwise signifies outer ring, counter-clockwise an inner ring 83 | // or hole. this logic was found at http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon- 84 | // points-are-in-clockwise-order 85 | function ringIsClockwise(ringToTest) { 86 | var total = 0,i = 0; 87 | var rLength = ringToTest.length; 88 | var pt1 = ringToTest[i]; 89 | var pt2; 90 | for (i; i < rLength - 1; i++) { 91 | pt2 = ringToTest[i + 1]; 92 | total += (pt2[0] - pt1[0]) * (pt2[1] + pt1[1]); 93 | pt1 = pt2; 94 | } 95 | return (total >= 0); 96 | } 97 | 98 | // This function ensures that rings are oriented in the right directions 99 | // outer rings are clockwise, holes are counterclockwise 100 | function orientRings(poly){ 101 | var output = []; 102 | var polygon = poly.slice(0); 103 | var outerRing = closeRing(polygon.shift().slice(0)); 104 | if(outerRing.length >= 4){ 105 | if(!ringIsClockwise(outerRing)){ 106 | outerRing.reverse(); 107 | } 108 | 109 | output.push(outerRing); 110 | 111 | for (var i = 0; i < polygon.length; i++) { 112 | var hole = closeRing(polygon[i].slice(0)); 113 | if(hole.length >= 4){ 114 | if(ringIsClockwise(hole)){ 115 | hole.reverse(); 116 | } 117 | output.push(hole); 118 | } 119 | } 120 | } 121 | 122 | return output; 123 | } 124 | 125 | // This function flattens holes in multipolygons to one array of polygons 126 | // [ 127 | // [ 128 | // [ array of outer coordinates ] 129 | // [ hole coordinates ] 130 | // [ hole coordinates ] 131 | // ], 132 | // [ 133 | // [ array of outer coordinates ] 134 | // [ hole coordinates ] 135 | // [ hole coordinates ] 136 | // ], 137 | // ] 138 | // becomes 139 | // [ 140 | // [ array of outer coordinates ] 141 | // [ hole coordinates ] 142 | // [ hole coordinates ] 143 | // [ array of outer coordinates ] 144 | // [ hole coordinates ] 145 | // [ hole coordinates ] 146 | // ] 147 | function flattenMultiPolygonRings(rings){ 148 | var output = []; 149 | for (var i = 0; i < rings.length; i++) { 150 | var polygon = orientRings(rings[i]); 151 | for (var x = polygon.length - 1; x >= 0; x--) { 152 | var ring = polygon[x].slice(0); 153 | output.push(ring); 154 | } 155 | } 156 | return output; 157 | } 158 | 159 | function coordinatesContainCoordinates(outer, inner){ 160 | var intersects = Terraformer.Tools.arraysIntersectArrays(outer, inner); 161 | var contains = Terraformer.Tools.coordinatesContainPoint(outer, inner[0]); 162 | if(!intersects && contains){ 163 | return true; 164 | } 165 | return false; 166 | } 167 | 168 | // do any polygons in this array contain any other polygons in this array? 169 | // used for checking for holes in arcgis rings 170 | function convertRingsToGeoJSON(rings){ 171 | var outerRings = []; 172 | var holes = []; 173 | var x; // iterator 174 | var outerRing; // current outer ring being evaluated 175 | var hole; // current hole being evaluated 176 | 177 | // for each ring 178 | for (var r = 0; r < rings.length; r++) { 179 | var ring = closeRing(rings[r].slice(0)); 180 | if(ring.length < 4){ 181 | continue; 182 | } 183 | // is this ring an outer ring? is it clockwise? 184 | if(ringIsClockwise(ring)){ 185 | var polygon = [ ring.slice().reverse() ]; // wind outer rings counterclockwise for RFC 7946 compliance 186 | outerRings.push(polygon); // push to outer rings 187 | } else { 188 | holes.push(ring.slice().reverse()); // wind inner rings clockwise for RFC 7946 compliance 189 | } 190 | } 191 | 192 | var uncontainedHoles = []; 193 | 194 | // while there are holes left... 195 | while(holes.length){ 196 | // pop a hole off out stack 197 | hole = holes.pop(); 198 | 199 | // loop over all outer rings and see if they contain our hole. 200 | var contained = false; 201 | for (x = outerRings.length - 1; x >= 0; x--) { 202 | outerRing = outerRings[x][0]; 203 | if(coordinatesContainCoordinates(outerRing, hole)){ 204 | // the hole is contained push it into our polygon 205 | outerRings[x].push(hole); 206 | contained = true; 207 | break; 208 | } 209 | } 210 | 211 | // ring is not contained in any outer ring 212 | // sometimes this happens https://github.com/Esri/esri-leaflet/issues/320 213 | if(!contained){ 214 | uncontainedHoles.push(hole); 215 | } 216 | } 217 | 218 | // if we couldn't match any holes using contains we can now try intersects... 219 | while(uncontainedHoles.length){ 220 | // pop a hole off out stack 221 | hole = uncontainedHoles.pop(); 222 | 223 | // loop over all outer rings and see if any intersect our hole. 224 | var intersects = false; 225 | for (x = outerRings.length - 1; x >= 0; x--) { 226 | outerRing = outerRings[x][0]; 227 | if(Terraformer.Tools.arraysIntersectArrays(outerRing, hole)){ 228 | // the hole intersects the outer ring push it into our polygon 229 | outerRings[x].push(hole); 230 | intersects = true; 231 | break; 232 | } 233 | } 234 | 235 | // hole does not intersect ANY outer ring at this point 236 | // make it an outer ring. 237 | if(!intersects) { 238 | outerRings.push([hole.reverse()]); 239 | } 240 | } 241 | 242 | if(outerRings.length === 1){ 243 | return { 244 | type: 'Polygon', 245 | coordinates: outerRings[0] 246 | }; 247 | } else { 248 | return { 249 | type: 'MultiPolygon', 250 | coordinates: outerRings 251 | }; 252 | } 253 | } 254 | 255 | // ArcGIS -> GeoJSON 256 | function parse(arcgis, options){ 257 | var geojson = {}; 258 | 259 | options = options || {}; 260 | options.idAttribute = options.idAttribute || undefined; 261 | 262 | if (arcgis.spatialReference && (arcgis.spatialReference.wkid === 3857 || arcgis.spatialReference.wkid === 102100)) { 263 | geojson.crs = Terraformer.MercatorCRS; 264 | } 265 | 266 | if(typeof arcgis.x === 'number' && typeof arcgis.y === 'number'){ 267 | geojson.type = "Point"; 268 | geojson.coordinates = [arcgis.x, arcgis.y]; 269 | if (arcgis.z || arcgis.m){ 270 | geojson.coordinates.push(arcgis.z); 271 | } 272 | if (arcgis.m){ 273 | geojson.coordinates.push(arcgis.m); 274 | } 275 | } 276 | 277 | if(arcgis.points){ 278 | geojson.type = "MultiPoint"; 279 | geojson.coordinates = arcgis.points.slice(0); 280 | } 281 | 282 | if(arcgis.paths) { 283 | if(arcgis.paths.length === 1){ 284 | geojson.type = "LineString"; 285 | geojson.coordinates = arcgis.paths[0].slice(0); 286 | } else { 287 | geojson.type = "MultiLineString"; 288 | geojson.coordinates = arcgis.paths.slice(0); 289 | } 290 | } 291 | 292 | if(arcgis.rings) { 293 | geojson = convertRingsToGeoJSON(arcgis.rings.slice(0)); 294 | } 295 | 296 | if( 297 | typeof arcgis.xmin === "number" && 298 | typeof arcgis.ymin === "number" && 299 | typeof arcgis.xmax === "number" && 300 | typeof arcgis.ymax === "number" 301 | ) { 302 | geojson.type = "Polygon"; 303 | geojson.coordinates = [[ 304 | [arcgis.xmax, arcgis.ymax], 305 | [arcgis.xmin, arcgis.ymax], 306 | [arcgis.xmin, arcgis.ymin], 307 | [arcgis.xmax, arcgis.ymin], 308 | [arcgis.xmax, arcgis.ymax] 309 | ]]; 310 | } 311 | 312 | if(arcgis.compressedGeometry || arcgis.geometry || arcgis.attributes) { 313 | geojson.type = "Feature"; 314 | 315 | if(arcgis.compressedGeometry){ 316 | arcgis.geometry = { 317 | paths: [ 318 | decompressGeometry(arcgis.compressedGeometry) 319 | ] 320 | }; 321 | } 322 | 323 | geojson.geometry = (arcgis.geometry) ? parse(arcgis.geometry) : null; 324 | geojson.properties = (arcgis.attributes) ? clone(arcgis.attributes) : null; 325 | if(arcgis.attributes) { 326 | geojson.id = arcgis.attributes[options.idAttribute] || arcgis.attributes.OBJECTID || arcgis.attributes.FID; 327 | } 328 | } 329 | 330 | return new Terraformer.Primitive(geojson); 331 | } 332 | 333 | // GeoJSON -> ArcGIS 334 | function convert(geojson, options){ 335 | var spatialReference; 336 | 337 | options = options || {}; 338 | var idAttribute = options.idAttribute || "OBJECTID"; 339 | 340 | if(options.sr){ 341 | spatialReference = { wkid: options.sr }; 342 | } else if (geojson && geojson.crs && geojson.crs.properties.name != "urn:ogc:def:crs:OGC:1.3:CRS84") { 343 | spatialReference = null; 344 | } else { 345 | spatialReference = { wkid: 4326 }; 346 | } 347 | 348 | var result = {}; 349 | var i; 350 | 351 | switch(geojson.type){ 352 | case "Point": 353 | result.x = geojson.coordinates[0]; 354 | result.y = geojson.coordinates[1]; 355 | if(geojson.coordinates[2]) { 356 | result.z = geojson.coordinates[2]; 357 | } 358 | if(geojson.coordinates[3]) { 359 | result.m = geojson.coordinates[3]; 360 | } 361 | result.spatialReference = spatialReference; 362 | break; 363 | case "MultiPoint": 364 | result.points = geojson.coordinates.slice(0); 365 | result.spatialReference = spatialReference; 366 | break; 367 | case "LineString": 368 | result.paths = [geojson.coordinates.slice(0)]; 369 | result.spatialReference = spatialReference; 370 | break; 371 | case "MultiLineString": 372 | result.paths = geojson.coordinates.slice(0); 373 | result.spatialReference = spatialReference; 374 | break; 375 | case "Polygon": 376 | result.rings = orientRings(geojson.coordinates.slice(0)); 377 | result.spatialReference = spatialReference; 378 | break; 379 | case "MultiPolygon": 380 | result.rings = flattenMultiPolygonRings(geojson.coordinates.slice(0)); 381 | result.spatialReference = spatialReference; 382 | break; 383 | case "Feature": 384 | if(geojson.geometry) { 385 | result.geometry = convert(geojson.geometry, options); 386 | } 387 | result.attributes = (geojson.properties) ? clone(geojson.properties) : {}; 388 | if(geojson.id) { 389 | result.attributes[idAttribute] = geojson.id; 390 | } 391 | break; 392 | case "FeatureCollection": 393 | result = []; 394 | for (i = 0; i < geojson.features.length; i++){ 395 | result.push(convert(geojson.features[i], options)); 396 | } 397 | break; 398 | case "GeometryCollection": 399 | result = []; 400 | for (i = 0; i < geojson.geometries.length; i++){ 401 | result.push(convert(geojson.geometries[i], options)); 402 | } 403 | break; 404 | } 405 | 406 | return result; 407 | } 408 | 409 | function parseCompressedGeometry(string){ 410 | return new Terraformer.LineString(decompressGeometry(string)); 411 | } 412 | 413 | exports.parse = parse; 414 | exports.convert = convert; 415 | exports.toGeoJSON = parse; 416 | exports.fromGeoJSON = convert; 417 | exports.parseCompressedGeometry = parseCompressedGeometry; 418 | 419 | return exports; 420 | })); 421 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import * as TerraformerArcGIS from "./index"; 2 | 3 | console.assert(typeof TerraformerArcGIS !== undefined); 4 | 5 | // parse an ArcGIS Geometry to GeoJSON 6 | let geojsonPoint = TerraformerArcGIS.parse({ 7 | x: -122.6764, 8 | y: 45.5165, 9 | spatialReference: { 10 | wkid: 4326 11 | } 12 | }); 13 | 14 | // convert a GeoJSON object into an ArcGIS geometry 15 | let arcgisPoint = TerraformerArcGIS.convert(geojsonPoint); 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "noEmit": true 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "object-literal-sort-keys": false, 5 | "trailing-comma": [ 6 | true, { 7 | "singleline": "never", 8 | "multiline": "never" 9 | } 10 | ], 11 | "interface-name": [ 12 | true, "never-prefix" 13 | ] 14 | } 15 | } --------------------------------------------------------------------------------