├── .github └── workflows │ ├── main.yml │ ├── publish-dry-run.yml │ └── publish.yml ├── .gitignore ├── .ort.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin ├── beta-terms.txt ├── h3ZoomLevelResolutionMap.json ├── h3resolutionRadiusMap.json ├── pro-beta-terms.txt └── zoomLevelsMap.json ├── declarations └── @turf │ ├── helpers.d.ts │ ├── lib │ └── geojson.d.ts │ └── voronoi.d.ts ├── docs ├── configure.md ├── geospace.md ├── transform.md └── xyzspace.md ├── package-lock.json ├── package.json ├── scripts └── publish-npm-gha.sh ├── src ├── api-error.ts ├── catalogUtil.ts ├── common.ts ├── geocodeUtil.ts ├── gisUtil.ts ├── here-catalog.ts ├── here-configure.ts ├── here-geocode.ts ├── here-interactivemap.ts ├── here-studio.ts ├── here-transform.ts ├── here-xyz.ts ├── here.ts ├── hexbin.ts ├── requestAsync.ts ├── sso.ts ├── summary.ts ├── transformutil.ts └── xyzutil.ts ├── test ├── data │ ├── sample.csv │ ├── sample.geojson │ ├── sample.geojsonl │ ├── sample.gpx │ └── shapesample │ │ ├── shapesample.dbf │ │ ├── shapesample.prj │ │ ├── shapesample.shp │ │ └── shapesample.shx ├── server.js └── test.js └── tsconfig.json /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build-test 2 | on: 3 | push: 4 | pull_request: 5 | branches: 6 | - master 7 | jobs: 8 | build-test: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | node-version: [10.x, 12.15.0] 14 | name: OS ${{ matrix.os }} - node ${{ matrix.node-version }} 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup node 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | 22 | - name: Cache node_modules directory 23 | uses: actions/cache@v2 24 | id: node_modules-cache 25 | with: 26 | path: node_modules 27 | key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }} 28 | 29 | - name: Install NPM packages 30 | if: steps.node_modules-cache.outputs.cache-hit != 'true' 31 | run: npm ci 32 | 33 | - name: Run tests 34 | run: | 35 | npm link 36 | npm test 37 | -------------------------------------------------------------------------------- /.github/workflows/publish-dry-run.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish-dry-run 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | types: [closed] 7 | jobs: 8 | publish-dry-run: 9 | if: github.event.pull_request.merged == true 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup node 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: 12.15.0 17 | 18 | - name: Dry run publish 19 | run: | 20 | npm install 21 | npm publish --dry-run 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | npm-publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Setup node 11 | uses: actions/setup-node@v2 12 | with: 13 | node-version: 12.15.0 14 | 15 | - name: Check tag version 16 | id: check_tag 17 | run: | 18 | if [[ ${{ github.ref }} =~ ^refs/tags/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 19 | echo "Tag matches expected format." 20 | echo "::set-output name=tag_match::true" 21 | fi 22 | 23 | - name: Publish 24 | if: steps.check_tag.outputs.tag_match == 'true' 25 | run: ./scripts/publish-npm-gha.sh 26 | env: 27 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | *.tgz 3 | bin/*.js 4 | .idea 5 | .vscode/launch.json 6 | -------------------------------------------------------------------------------- /.ort.yml: -------------------------------------------------------------------------------- 1 | excludes: 2 | paths: 3 | - pattern: "docs/**" 4 | reason: "DOCUMENTATION_OF" 5 | comment: "Directory is only used for doucmentation. Not included in released artifacts." 6 | - pattern: "test/**" 7 | reason: "TEST_OF" 8 | comment: "Directory is only used for testing. Not included in released artifacts." 9 | scopes: 10 | - name: "devDependencies" 11 | reason: "DEV_DEPENDENCY_OF" 12 | comment: "Packages for development only. Not included in released artifacts." -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.2.1](https://github.com/heremaps/here-cli/tree/0.2.1) (2019-02-22) 4 | [Full Changelog](https://github.com/heremaps/here-cli/compare/0.1.0...0.2.1) 5 | 6 | **Closed issues:** 7 | 8 | - Tool doesn't support appId / appCode containing a minus sign [\#2](https://github.com/heremaps/here-cli/issues/2) 9 | 10 | **Merged pull requests:** 11 | 12 | - ClientId in hub api calls [\#33](https://github.com/heremaps/here-cli/pull/33) ([ashsharm](https://github.com/ashsharm)) 13 | - Added Travis CI Configuration for HERE CLI [\#32](https://github.com/heremaps/here-cli/pull/32) ([cpandit201](https://github.com/cpandit201)) 14 | - Updated package-lock.json [\#30](https://github.com/heremaps/here-cli/pull/30) ([dhaneeshtnair](https://github.com/dhaneeshtnair)) 15 | - NPM Tests [\#27](https://github.com/heremaps/here-cli/pull/27) ([dhaneeshtnair](https://github.com/dhaneeshtnair)) 16 | - fixing help text description [\#24](https://github.com/heremaps/here-cli/pull/24) ([Naitik333](https://github.com/Naitik333)) 17 | - 0.2.0 release fixes [\#23](https://github.com/heremaps/here-cli/pull/23) ([tsteenbe](https://github.com/tsteenbe)) 18 | - npm vulnarabilities [\#22](https://github.com/heremaps/here-cli/pull/22) ([dhaneeshtnair](https://github.com/dhaneeshtnair)) 19 | - converted json message to string on error [\#21](https://github.com/heremaps/here-cli/pull/21) ([dhaneeshtnair](https://github.com/dhaneeshtnair)) 20 | - Stream geojsonl [\#20](https://github.com/heremaps/here-cli/pull/20) ([dhaneeshtnair](https://github.com/dhaneeshtnair)) 21 | - Fixing miner bugs [\#15](https://github.com/heremaps/here-cli/pull/15) ([Naitik333](https://github.com/Naitik333)) 22 | - Merge pull request \#13 from heremaps/stream\_geojsonl [\#14](https://github.com/heremaps/here-cli/pull/14) ([Naitik333](https://github.com/Naitik333)) 23 | - Stream geojsonl [\#13](https://github.com/heremaps/here-cli/pull/13) ([Naitik333](https://github.com/Naitik333)) 24 | - Stream geojsonl [\#11](https://github.com/heremaps/here-cli/pull/11) ([Naitik333](https://github.com/Naitik333)) 25 | - fixing issues of list,token,upload [\#10](https://github.com/heremaps/here-cli/pull/10) ([Naitik333](https://github.com/Naitik333)) 26 | - Fix accessing xyz [\#9](https://github.com/heremaps/here-cli/pull/9) ([haraldF](https://github.com/haraldF)) 27 | - Add missing license header [\#8](https://github.com/heremaps/here-cli/pull/8) ([haraldF](https://github.com/haraldF)) 28 | - Port to TypeScript [\#7](https://github.com/heremaps/here-cli/pull/7) ([haraldF](https://github.com/haraldF)) 29 | - supportting old key separator [\#6](https://github.com/heremaps/here-cli/pull/6) ([dhaneeshtnair](https://github.com/dhaneeshtnair)) 30 | - changes for supportting geojsonl [\#5](https://github.com/heremaps/here-cli/pull/5) ([dhaneeshtnair](https://github.com/dhaneeshtnair)) 31 | - handle tags that are numbers [\#4](https://github.com/heremaps/here-cli/pull/4) ([burritojustice](https://github.com/burritojustice)) 32 | - Add error handling [\#3](https://github.com/heremaps/here-cli/pull/3) ([haraldF](https://github.com/haraldF)) 33 | - Fix link to CONTRIBUTING.md [\#1](https://github.com/heremaps/here-cli/pull/1) ([haraldF](https://github.com/haraldF)) 34 | 35 | ## [0.1.0](https://github.com/heremaps/here-cli/tree/0.1.0) (2018-09-28) 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page 2 | 3 | ## Code Reviews 4 | All submissions, including submissions by project members, require review. We 5 | use [Github pull requests](https://help.github.com/articles/about-pull-requests/) for this purpose. 6 | 7 | ### Signing each Commit 8 | 9 | As part of filing a pull request we ask you to sign off the 10 | [Developer Certificate of Origin](https://developercertificate.org/) (DCO) in each commit. 11 | Any Pull Request with commits that are not signed off will be reject by the 12 | [DCO check](https://probot.github.io/apps/dco/). 13 | 14 | A DCO is lightweight way for a contributor to confirm that you wrote or otherwise have the right 15 | to submit code or documentation to a project. Simply add `Signed-off-by` as shown in the example below 16 | to indicate that you agree with the DCO. 17 | 18 | An example signed commit message: 19 | 20 | ``` 21 | README.md: Fix minor spelling mistake 22 | 23 | Signed-off-by: John Doe 24 | ``` 25 | 26 | Git has the `-s` flag that can sign a commit for you, see example below: 27 | 28 | `$ git commit -s -m 'README.md: Fix minor spelling mistake'` 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (C) 2018-2021 HERE Europe B.V. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HERE CLI 2 | [![Build Status](https://travis-ci.com/heremaps/here-cli.svg?branch=master)](https://travis-ci.com/heremaps/here-cli) 3 | 4 | HERE CLI is a Node.js command-line interface to work with HERE APIs starting with [HERE Data Hub](https://www.here.xyz) APIs. Right now, it allows you to interact with HERE Data Hub to create and manage your Spaces, and easily upload and manage your datasets. 5 | 6 | ### Prerequisites 7 | 8 | HERE CLI is built on Node.js, a cross-platform efficient language to write even complex, local applications. 9 | 10 | To use the HERE CLI, you should have npm installed. The best way is to go to nodejs.org and install the appropriate package for your system (both 8.x LTS and 10.x Current should work). If you need 11 | help with this have a look at our [CLI Introduction Guide](https://www.here.xyz/cli/). 12 | 13 | ### Installing the CLI 14 | 15 | To install the HERE CLI use the following command. Depending on your system, you might need elevated permissions (like sudo) to install globally. 16 | 17 | ``` 18 | npm install -g @here/cli 19 | ``` 20 | 21 | If all goes well, you can check if the CLI is installed correctly by just runnning 22 | 23 | ``` 24 | here --help 25 | ``` 26 | 27 | 28 | ## Configure HERE CLI 29 | 30 | As the HERE CLI works with HERE APIs in the cloud, you need to configure your developer identity. 31 | This only needs to be done once. Just run `here configure` to set the `email` and `password`. 32 | For detailed information on getting a Developer account have a look at our [Getting Started Guide](https://www.here.xyz/getting-started/). 33 | 34 | ## Supported Commands 35 | 36 | The CLI currently support the following sub-commands: 37 | 38 | ``` 39 | - configure|c [verify|refresh] setup configuration for authentication 40 | - xyz|xs [list|create|upload] work with Data Hub spaces 41 | - studio [list|delete|show] work with HERE Studio projects 42 | - transform|tf [csv2geo|shp2geo|gpx2geo] convert from csv/shapefile/gpx to geojson 43 | - geocode|gc geocode feature 44 | - help [command] display help for command 45 | ``` 46 | 47 | ## Development 48 | 49 | ### Building the CLI 50 | 51 | To develop the CLI further and add new features, first clone this repository and install the 52 | npm dependencies. 53 | 54 | ``` 55 | git clone https://github.com/heremaps/here-cli.git 56 | npm install 57 | ``` 58 | 59 | Normally as a user you would install the CLI with a `-g` switch globally so that it can be 60 | used outside of a package directory. To make development easier it makes more sense not to 61 | that globally as that requires elevated permissions. 62 | 63 | You should test and debug the commands by running the respective .js file. We use 64 | [npm commander](https://www.npmjs.com/package/commander) to drive the command parsing and 65 | execution. To get a good 66 | understanding how it *feels* on the commandline use local linking to make the `bin` sources 67 | available as executable commands: 68 | 69 | ``` 70 | npm link 71 | ``` 72 | 73 | Finally to package and distribute a new release (which we would do, not you) we update and 74 | tag the version followed by 75 | 76 | ``` 77 | npm pack ... 78 | npm deploy ... 79 | ``` 80 | 81 | ### Contributing 82 | 83 | We encourage contributions. For fixes and improvements it's helpful to have an [issue](http://github.com/heremaps/here-cli/issues) to reference to. So please file them for us to provide focus. Also read the notes in [CONTRIBUTING.md](CONTRIBUTING.md). 84 | 85 | When you add a new sub-command (as `bin/here-commandname.js`) please make sure to also include the relevant documentation (as `docs/commandname.md`). 86 | 87 | If the command is interacting with a HERE service, please include a links to the relevant service documenation at [developer.here.com](https://developer.here.com/documentation). 88 | 89 | ## License 90 | 91 | Copyright (C) 2018 - 2021 HERE Europe B.V. 92 | 93 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 94 | 95 | 96 | -------------------------------------------------------------------------------- /bin/beta-terms.txt: -------------------------------------------------------------------------------- 1 | Welcome to the HERE XYZ! 2 | Please review the terms and conditions https://developer.here.com/terms-and-conditions 3 | -------------------------------------------------------------------------------- /bin/h3ZoomLevelResolutionMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "0":2, 3 | "1":2, 4 | "2":2, 5 | "3":2, 6 | "4":3, 7 | "5":4, 8 | "6":4, 9 | "7":5, 10 | "8":6, 11 | "9":6, 12 | "10":7, 13 | "11":8, 14 | "12":9, 15 | "13":9, 16 | "14":10, 17 | "15":11, 18 | "16":11, 19 | "17":12, 20 | "18":13, 21 | "19":14, 22 | "20":14, 23 | "21":15, 24 | "22":15 25 | } -------------------------------------------------------------------------------- /bin/h3resolutionRadiusMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "0":1107.712591000, 3 | "1":418.676005500, 4 | "2":158.244655800, 5 | "3":59.810857940, 6 | "4":22.606379400, 7 | "5":8.544408276, 8 | "6":3.229482772, 9 | "7":1.220629759, 10 | "8":0.461354684, 11 | "9":0.174375668, 12 | "10":0.065907807, 13 | "11":0.024910561, 14 | "12":0.009415526, 15 | "13":0.003559893, 16 | "14":0.001348575, 17 | "15":0.000509713 18 | } -------------------------------------------------------------------------------- /bin/pro-beta-terms.txt: -------------------------------------------------------------------------------- 1 | In order to use the HERE XYZ Pro Beta functionality, you will need to (A)ccept the Beta license agreement 2 | ( https://legal.here.com/en-gb/HERE-XYZ-Pro-Beta-Terms-and-Conditions ) 3 | 4 | -------------------------------------------------------------------------------- /bin/zoomLevelsMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "1":409600, 3 | "2":204800, 4 | "3":102400, 5 | "4":51200, 6 | "5":25600, 7 | "6":12800, 8 | "7":6400, 9 | "8":3200, 10 | "9":1600, 11 | "10":800, 12 | "11":400, 13 | "12":200, 14 | "13":100, 15 | "14":50, 16 | "15":25, 17 | "16":12, 18 | "17":6, 19 | "18":3 20 | } -------------------------------------------------------------------------------- /declarations/@turf/helpers.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Id, Properties, BBox, Position, 3 | Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, 4 | GeometryObject, GeoJSONObject, GeometryCollection, Geometry, 5 | GeometryTypes, Types, CollectionTypes, Geometries, 6 | Feature, FeatureCollection 7 | } from './lib/geojson' 8 | export { 9 | Id, Properties, BBox, Position, 10 | Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, 11 | GeometryObject, GeoJSONObject, GeometryCollection, Geometry, 12 | GeometryTypes, Types, CollectionTypes, Geometries, 13 | Feature, FeatureCollection 14 | } 15 | 16 | // TurfJS Combined Types 17 | export type Coord = Feature | Point | Position; 18 | 19 | // TurfJS String Types 20 | export type Units = 'miles' | 'nauticalmiles' | 'degrees' | 'radians' | 'inches' | 'yards' | 'meters' | 'metres' | 'kilometers' | 'kilometres'; 21 | export type Grid = 'point' | 'square' | 'hex' | 'triangle'; 22 | export type Corners = 'sw' | 'se' | 'nw' | 'ne' | 'center' | 'centroid'; 23 | 24 | export type Lines = LineString | MultiLineString | Polygon | MultiPolygon; 25 | export type AllGeoJSON = Feature | FeatureCollection | Geometry | GeometryCollection; 26 | 27 | interface FeatureOptions { 28 | id?: Id; 29 | bbox?: BBox; 30 | } 31 | 32 | interface GeometryOptions { 33 | bbox?: BBox; 34 | } 35 | 36 | /** 37 | * http://turfjs.org/docs/#feature 38 | */ 39 | export function feature(geometry: G, properties?: P, options?: FeatureOptions): Feature; 40 | 41 | /** 42 | * http://turfjs.org/docs/#featurecollection 43 | */ 44 | export function featureCollection(features: Feature[], options?: FeatureOptions): FeatureCollection; 45 | export function featureCollection(features: Feature[], options?: FeatureOptions): FeatureCollection; 46 | 47 | /** 48 | * http://turfjs.org/docs/#geometry 49 | */ 50 | export function geometry(type: 'Point', coordinates: Position, options?: GeometryOptions): Point; 51 | export function geometry(type: 'LineString', coordinates: Position[], options?: GeometryOptions): LineString; 52 | export function geometry(type: 'Polygon', coordinates: Position[][], options?: GeometryOptions): Polygon; 53 | export function geometry(type: 'MultiPoint', coordinates: Position[], options?: GeometryOptions): MultiPoint; 54 | export function geometry(type: 'MultiLineString', coordinates: Position[][], options?: GeometryOptions): MultiLineString; 55 | export function geometry(type: 'MultiPolygon', coordinates: Position[][][], options?: GeometryOptions): MultiPolygon; 56 | export function geometry(type: string, coordinates: any[], options?: GeometryOptions): Geometry; 57 | 58 | /** 59 | * http://turfjs.org/docs/#point 60 | */ 61 | export function point

(coordinates: Position, properties?: P, options?: FeatureOptions): Feature; 62 | 63 | /** 64 | * http://turfjs.org/docs/#points 65 | */ 66 | export function points

(coordinates: Position[], properties?: P, options?: FeatureOptions): FeatureCollection; 67 | 68 | /** 69 | * http://turfjs.org/docs/#polygon 70 | */ 71 | export function polygon

(coordinates: Position[][], properties?: P, options?: FeatureOptions): Feature; 72 | 73 | /** 74 | * http://turfjs.org/docs/#polygons 75 | */ 76 | export function polygons

(coordinates: Position[][][], properties?: P, options?: FeatureOptions): FeatureCollection; 77 | 78 | /** 79 | * http://turfjs.org/docs/#linestring 80 | */ 81 | export function lineString

(coordinates: Position[], properties?: P, options?: FeatureOptions): Feature; 82 | 83 | /** 84 | * http://turfjs.org/docs/#linestrings 85 | */ 86 | export function lineStrings

(coordinates: Position[][], properties?: P, options?: FeatureOptions): FeatureCollection; 87 | 88 | 89 | /** 90 | * http://turfjs.org/docs/#multilinestring 91 | */ 92 | export function multiLineString

(coordinates: Position[][], properties?: P, options?: FeatureOptions): Feature; 93 | 94 | /** 95 | * http://turfjs.org/docs/#multipoint 96 | */ 97 | export function multiPoint

(coordinates: Position[], properties?: P, options?: FeatureOptions): Feature; 98 | 99 | /** 100 | * http://turfjs.org/docs/#multipolygon 101 | */ 102 | export function multiPolygon

(coordinates: Position[][][], properties?: P, options?: FeatureOptions): Feature; 103 | 104 | /** 105 | * http://turfjs.org/docs/#geometrycollection 106 | */ 107 | export function geometryCollection

(geometries: Geometries[], properties?: P, options?: FeatureOptions): Feature; 108 | 109 | /** 110 | * http://turfjs.org/docs/#radianstolength 111 | */ 112 | export function radiansToLength(radians: number, units?: Units): number; 113 | 114 | /** 115 | * http://turfjs.org/docs/#lengthtoradians 116 | */ 117 | export function lengthToRadians(distance: number, units?: Units): number; 118 | 119 | /** 120 | * http://turfjs.org/docs/#lengthtodegrees 121 | */ 122 | export function lengthToDegrees(distance: number, units?: Units): number; 123 | 124 | /** 125 | * http://turfjs.org/docs/#bearingtoazimuth 126 | */ 127 | export function bearingToAzimuth(bearing: number): number; 128 | 129 | /** 130 | * http://turfjs.org/docs/#radianstodegrees 131 | */ 132 | export function radiansToDegrees(radians: number): number; 133 | 134 | /** 135 | * http://turfjs.org/docs/#degreestoradians 136 | */ 137 | export function degreesToRadians(degrees: number): number; 138 | 139 | /** 140 | * http://turfjs.org/docs/#round 141 | */ 142 | export function round(num: number, precision?: number): number; 143 | 144 | /** 145 | * http://turfjs.org/docs/#convertlength 146 | */ 147 | export function convertLength(length: number, originalUnit: Units, finalUnit?: Units): number; 148 | 149 | /** 150 | * http://turfjs.org/docs/#convertarea 151 | */ 152 | export function convertArea(area: number, originalUnit?: Units, finalUnit?: Units): number; 153 | 154 | /** 155 | * http://turfjs.org/docs/#isnumber 156 | */ 157 | export function isNumber(num: any): boolean; 158 | 159 | /** 160 | * http://turfjs.org/docs/#isobject 161 | */ 162 | export function isObject(input: any): boolean; 163 | 164 | /** 165 | * Earth Radius used with the Harvesine formula and approximates using a spherical (non-ellipsoid) Earth. 166 | */ 167 | export const earthRadius: number; 168 | 169 | /** 170 | * Unit of measurement factors using a spherical (non-ellipsoid) earth radius. 171 | */ 172 | export const factors: { 173 | meters: number; 174 | millimeters: number; 175 | centimeters: number; 176 | kilometers: number; 177 | miles: number; 178 | nauticalmiles: number; 179 | inches: number; 180 | yards: number; 181 | feet: number; 182 | } 183 | 184 | /** 185 | * Units of measurement factors based on 1 meter. 186 | */ 187 | export const unitsFactors: { 188 | meters: number; 189 | millimeters: number; 190 | centimeters: number; 191 | kilometers: number; 192 | miles: number; 193 | nauticalmiles: number; 194 | inches: number; 195 | yards: number; 196 | feet: number; 197 | radians: number; 198 | degrees: number; 199 | }; 200 | 201 | /** 202 | * Area of measurement factors based on 1 square meter. 203 | */ 204 | export const areaFactors: { 205 | meters: number; 206 | millimeters: number; 207 | centimeters: number; 208 | kilometers: number; 209 | acres: number; 210 | miles: number; 211 | yards: number; 212 | feet: number; 213 | inches: number; 214 | }; 215 | 216 | /** 217 | * Validate Id 218 | */ 219 | export function validateId(id: Id): void; 220 | 221 | /** 222 | * Validate BBox 223 | */ 224 | export function validateBBox(bbox: BBox): void; 225 | -------------------------------------------------------------------------------- /declarations/@turf/lib/geojson.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for geojson 7946.0 2 | // Project: https://geojson.org/ 3 | // Definitions by: Jacob Bruun 4 | // Arne Schubert 5 | // Jeff Jacobson 6 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 7 | // TypeScript Version: 2.3 8 | 9 | // Note: as of the RFC 7946 version of GeoJSON, Coordinate Reference Systems 10 | // are no longer supported. (See https://tools.ietf.org/html/rfc7946#appendix-B)} 11 | 12 | // export as namespace GeoJSON; 13 | 14 | /** 15 | * GeometryTypes 16 | * 17 | * https://tools.ietf.org/html/rfc7946#section-1.4 18 | * The valid values for the "type" property of GeoJSON geometry objects. 19 | */ 20 | export type GeometryTypes = "Point" | 21 | "LineString" | 22 | "Polygon" | 23 | "MultiPoint" | 24 | "MultiLineString" | 25 | "MultiPolygon"; 26 | 27 | export type CollectionTypes = "FeatureCollection" | "GeometryCollection"; 28 | 29 | /** 30 | * Types 31 | * 32 | * https://tools.ietf.org/html/rfc7946#section-1.4 33 | * The value values for the "type" property of GeoJSON Objects. 34 | */ 35 | export type Types = "Feature" | GeometryTypes | CollectionTypes; 36 | 37 | /** 38 | * Bounding box 39 | * 40 | * https://tools.ietf.org/html/rfc7946#section-5 41 | * A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections. 42 | * The value of the bbox member MUST be an array of length 2*n where n is the number of dimensions represented in the contained geometries, 43 | * with all axes of the most southwesterly point followed by all axes of the more northeasterly point. 44 | * The axes order of a bbox follows the axes order of geometries. 45 | */ 46 | export type BBox2d = [number, number, number, number]; 47 | export type BBox3d = [number, number, number, number, number, number]; 48 | export type BBox = BBox2d | BBox3d; 49 | 50 | /** 51 | * Id 52 | * 53 | * https://tools.ietf.org/html/rfc7946#section-3.2 54 | * If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of 55 | * the Feature object with the name "id", and the value of this member is either a JSON string or number. 56 | */ 57 | export type Id = string | number; 58 | 59 | /** 60 | * Position 61 | * 62 | * https://tools.ietf.org/html/rfc7946#section-3.1.1 63 | * Array should contain between two and three elements. 64 | * The previous GeoJSON specification allowed more elements (e.g., which could be used to represent M values), 65 | * but the current specification only allows X, Y, and (optionally) Z to be defined. 66 | */ 67 | export type Position = number[]; // [number, number] | [number, number, number]; 68 | 69 | /** 70 | * Properties 71 | * 72 | * https://tools.ietf.org/html/rfc7946#section-3.2 73 | * A Feature object has a member with the name "properties". 74 | * The value of the properties member is an object (any JSON object or a JSON null value). 75 | */ 76 | export type Properties = { [name: string]: any; } | null; 77 | 78 | /** 79 | * Geometries 80 | */ 81 | export type Geometries = Point | 82 | LineString | 83 | Polygon | 84 | MultiPoint | 85 | MultiLineString | 86 | MultiPolygon; 87 | 88 | /** 89 | * GeoJSON Object 90 | * 91 | * https://tools.ietf.org/html/rfc7946#section-3 92 | * The GeoJSON specification also allows [foreign members](https://tools.ietf.org/html/rfc7946#section-6.1) 93 | * Developers should use "&" type in TypeScript or extend the interface to add these foreign members. 94 | */ 95 | export interface GeoJSONObject { 96 | // Don't include foreign members directly into this type def. 97 | // in order to preserve type safety. 98 | // [key: string]: any; 99 | /** 100 | * Specifies the type of GeoJSON object. 101 | */ 102 | type: string; 103 | /** 104 | * Bounding box of the coordinate range of the object's Geometries, Features, or Feature Collections. 105 | * https://tools.ietf.org/html/rfc7946#section-5 106 | */ 107 | bbox?: BBox; 108 | } 109 | 110 | /** 111 | * Geometry Object 112 | * 113 | * https://tools.ietf.org/html/rfc7946#section-3 114 | */ 115 | export interface GeometryObject extends GeoJSONObject { 116 | type: GeometryTypes; 117 | } 118 | 119 | /** 120 | * Geometry 121 | * 122 | * https://tools.ietf.org/html/rfc7946#section-3 123 | */ 124 | export interface Geometry extends GeoJSONObject { 125 | coordinates: Position | 126 | Position[] | 127 | Position[][] | 128 | Position[][][]; 129 | } 130 | 131 | /** 132 | * Point Geometry Object 133 | * 134 | * https://tools.ietf.org/html/rfc7946#section-3.1.2 135 | */ 136 | export interface Point extends GeometryObject { 137 | type: "Point"; 138 | coordinates: Position; 139 | } 140 | 141 | /** 142 | * MultiPoint Geometry Object 143 | * 144 | * https://tools.ietf.org/html/rfc7946#section-3.1.3 145 | */ 146 | export interface MultiPoint extends GeometryObject { 147 | type: "MultiPoint"; 148 | coordinates: Position[]; 149 | } 150 | 151 | /** 152 | * LineString Geometry Object 153 | * 154 | * https://tools.ietf.org/html/rfc7946#section-3.1.4 155 | */ 156 | export interface LineString extends GeometryObject { 157 | type: "LineString"; 158 | coordinates: Position[]; 159 | } 160 | 161 | /** 162 | * MultiLineString Geometry Object 163 | * 164 | * https://tools.ietf.org/html/rfc7946#section-3.1.5 165 | */ 166 | export interface MultiLineString extends GeometryObject { 167 | type: "MultiLineString"; 168 | coordinates: Position[][]; 169 | } 170 | 171 | /** 172 | * Polygon Geometry Object 173 | * 174 | * https://tools.ietf.org/html/rfc7946#section-3.1.6 175 | */ 176 | export interface Polygon extends GeometryObject { 177 | type: "Polygon"; 178 | coordinates: Position[][]; 179 | } 180 | 181 | /** 182 | * MultiPolygon Geometry Object 183 | * 184 | * https://tools.ietf.org/html/rfc7946#section-3.1.7 185 | */ 186 | export interface MultiPolygon extends GeometryObject { 187 | type: "MultiPolygon"; 188 | coordinates: Position[][][]; 189 | } 190 | 191 | /** 192 | * GeometryCollection 193 | * 194 | * https://tools.ietf.org/html/rfc7946#section-3.1.8 195 | * 196 | * A GeoJSON object with type "GeometryCollection" is a Geometry object. 197 | * A GeometryCollection has a member with the name "geometries". 198 | * The value of "geometries" is an array. Each element of this array is a GeoJSON Geometry object. 199 | * It is possible for this array to be empty. 200 | */ 201 | export interface GeometryCollection extends GeoJSONObject { 202 | type: "GeometryCollection"; 203 | geometries: Geometries[]; 204 | } 205 | 206 | /** 207 | * Feature 208 | * 209 | * https://tools.ietf.org/html/rfc7946#section-3.2 210 | * A Feature object represents a spatially bounded thing. 211 | * Every Feature object is a GeoJSON object no matter where it occurs in a GeoJSON text. 212 | */ 213 | export interface Feature extends GeoJSONObject { 214 | type: "Feature"; 215 | geometry: G | null; 216 | /** 217 | * A value that uniquely identifies this feature in a 218 | * https://tools.ietf.org/html/rfc7946#section-3.2. 219 | */ 220 | id?: Id; 221 | /** 222 | * Properties associated with this feature. 223 | */ 224 | properties: P | null; 225 | } 226 | 227 | /** 228 | * Feature Collection 229 | * 230 | * https://tools.ietf.org/html/rfc7946#section-3.3 231 | * A GeoJSON object with the type "FeatureCollection" is a FeatureCollection object. 232 | * A FeatureCollection object has a member with the name "features". 233 | * The value of "features" is a JSON array. Each element of the array is a Feature object as defined above. 234 | * It is possible for this array to be empty. 235 | */ 236 | export interface FeatureCollection extends GeoJSONObject { 237 | features: Array>; 238 | } 239 | -------------------------------------------------------------------------------- /declarations/@turf/voronoi.d.ts: -------------------------------------------------------------------------------- 1 | import { FeatureCollection, BBox, Point, Polygon } from '@turf/helpers'; 2 | 3 | /** 4 | * http://turfjs.org/docs/#voronoi 5 | * separate declaration file due to wrong typescript in voronoi - https://github.com/Turfjs/turf/issues/1422 6 | */ 7 | export default function voronoi( 8 | points: FeatureCollection, 9 | options: {bbox: BBox} 10 | ): FeatureCollection; 11 | -------------------------------------------------------------------------------- /docs/configure.md: -------------------------------------------------------------------------------- 1 | #### configure 2 | 3 | ``` 4 | here configure 5 | ``` 6 | 7 | Configure mapcli with HERE AppId and AppCode. 8 | Login to HERE [Developer Portal](https://developer.here.com/) for creating a developer account 9 | 10 | #### configure verify 11 | 12 | ``` 13 | here configure verify 14 | ``` -------------------------------------------------------------------------------- /docs/geospace.md: -------------------------------------------------------------------------------- 1 | 2 | #### List all geospaces 3 | 4 | ``` 5 | here geospace list 6 | ``` 7 | 8 | This command will list all the geospaces for which the user is authorized. 9 | 10 | #### Create a new geospace 11 | 12 | ``` 13 | here geospace create x-samplegeospace -t "sample test geospace" -d "sample creation" 14 | ``` 15 | 16 | Create a new geospace with name x-samplegeospace. 17 | 18 | ##### Options 19 | 20 | -t title 21 | 22 | -d description 23 | 24 | #### Upload/Update data to geospace 25 | 26 | ``` 27 | here geospace upload x-testgeospace -f /Users/dhatb/data.geojson 28 | ``` 29 | 30 | Upload data to geospace with name x-testgeospace 31 | 32 | ##### Options 33 | 34 | -f path to file name 35 | 36 | #### View a geospace 37 | 38 | ``` 39 | here geospace show x-testgeospace 40 | ``` 41 | 42 | List the objects of a geospace 43 | 44 | ##### Options 45 | 46 | -l limit count 47 | 48 | -h offset handle 49 | 50 | #### Delete a geospace 51 | 52 | ``` 53 | here geospace delete x-testgeospace 54 | ``` 55 | 56 | Delete a geospace on which the user is authorised to. 57 | 58 | -------------------------------------------------------------------------------- /docs/transform.md: -------------------------------------------------------------------------------- 1 | #### Transform csv to geojson 2 | - ##### Convert the provided csv file as geojson 3 | ``` 4 | here transform csv2geo /Users/dhatb/mapcli_publish/mapcli/bin/test.csv 5 | ``` 6 | ###### system will try to autodetect the latitude and longitude fieldname from the following matched field names. 7 | - longitude -> "x", "xcoord", "xcoordinate", "coordx", "coordinatex", "longitude", "lon" 8 | - latitudde -> "y", "ycoord", "ycoordinate", "coordy", "coordinatey", "latitude", "lat" 9 | 10 | ##### Options 11 | -y, --lat [lat] latitude field name 12 | -x, --lon [lon] longitude field name 13 | -d, --delimiter [,] delimiter for parsing csv 14 | -q, --quote ["] quote used for parsing csv 15 | -po, --point [point] points field name 16 | 17 | 18 | #### Transform shapefile to geojson 19 | - ##### Convert the provided shape file as geojson 20 | ``` 21 | here transform shp2geo /Users/dhatb/mapcli_publish/mapcli/bin/test.shp 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/xyzspace.md: -------------------------------------------------------------------------------- 1 | 2 | #### List all xyz spaces 3 | 4 | ``` 5 | here xyz list 6 | ``` 7 | 8 | This command will list all the xyzspaces for which the user is authorized. 9 | 10 | #### Create a new xyz space 11 | 12 | ``` 13 | here xyz create -t "sample test xyzspace" -d "sample creation" 14 | ``` 15 | 16 | Create a new xyzspace with name x-samplexyzspace. 17 | 18 | ##### Options 19 | 20 | -t title 21 | 22 | -d description 23 | 24 | #### Upload/Update data to xyz space 25 | - #### upload geojson 26 | ``` 27 | here xyz upload x-testxyzspace -f /Users/dhatb/data.geojson 28 | ``` 29 | Upload data to xyzspace with name x-testxyzspace 30 | ##### Options 31 | -f path to file name 32 | - #### upload csv 33 | ``` 34 | here xyz upload x-testxyzspace -f /Users/dhatb/data.csv 35 | ``` 36 | Upload data to xyzspace with name x-testxyzspace 37 | ##### Options 38 | -f path to file name 39 | -y, --lat [lat] latitude field name 40 | -x, --lon [lon] longitude field name 41 | -d, --delimiter [,] delimiter for parsing csv 42 | -q, --quote ["] quote used for parsing csv 43 | 44 | - #### upload shapefile 45 | ``` 46 | here xyz upload x-testxyzspace -f /Users/dhatb/data.shp 47 | ``` 48 | Upload data to xyzspace with name x-testxyzspace 49 | ##### Options 50 | - -f, --file geojson file to upload 51 | - -c, --chunk [chunk] chunk size 52 | - -t, --tags [tags] tags for the xyz space 53 | - -x, --lon [lon] longitude field name 54 | - -y, --lat [lat] latitude field name 55 | - -z, --alt [alt] altitude field name 56 | - -po, --point [point] points field name 57 | - -p, --ptag [ptag] property names to be used to add tag 58 | - -i, --id [id] property name(s) to be used as the feature ID 59 | - -a, --assign list the sample data and allows you to assign fields which needs to be selected as tags 60 | 61 | #### View a xyzspace 62 | 63 | ``` 64 | here xyz show x-testxyzspace 65 | ``` 66 | 67 | List the objects of a xyzspace 68 | 69 | ##### Options 70 | 71 | -l limit count 72 | 73 | -h offset handle 74 | 75 | #### Delete a xyzspace 76 | 77 | ``` 78 | here xyz delete x-testxyzspace 79 | ``` 80 | 81 | Delete a xyzspace on which the user is authorised to. 82 | 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@here/cli", 3 | "description": "CLI for HERE XYZ Platform", 4 | "author": "HERE Europe B.V.", 5 | "version": "1.9.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/heremaps/here-cli" 9 | }, 10 | "scripts": { 11 | "prepare": "npx tsc", 12 | "test": "npx tsc && npx mocha" 13 | }, 14 | "license": "MIT", 15 | "dependencies": { 16 | "@here/olp-sdk-authentication": "1.11.0", 17 | "@turf/turf": "6.3.0", 18 | "JSONStream": "^1.3.5", 19 | "block-queue": "0.0.2", 20 | "colors": "1.4.0", 21 | "commander": "6.0.0", 22 | "console.table": "0.10.0", 23 | "crypto-js": "4.0.0", 24 | "d3-delaunay": "5.3.0", 25 | "event-stream": "^4.0.1", 26 | "extract-zip": "2.0.1", 27 | "fast-csv": "^4.3.6", 28 | "geojson-validation": "^0.2.1", 29 | "geojson2h3": "1.0.1", 30 | "get-stdin": "8.0.0", 31 | "getmac": "5.10.0", 32 | "glob": "7.1.6", 33 | "got": "11.3.0", 34 | "h3-js": "3.7.0", 35 | "inquirer": "7.2.0", 36 | "latest-version": "5.1.0", 37 | "line-by-line": "^0.1.6", 38 | "moment": "2.27.0", 39 | "open": "7.0.4", 40 | "proj4": "2.6.2", 41 | "project-version": "1.2.0", 42 | "prompt": "^1.0.0", 43 | "readline": "1.3.0", 44 | "shapefile": "0.6.6", 45 | "table": "^6.7.5", 46 | "tmp": "0.2.1", 47 | "user-settings": "0.2.0", 48 | "xlsx": "^0.17.4", 49 | "@xmldom/xmldom": "^0.7.5" 50 | }, 51 | "devDependencies": { 52 | "@types/crypto-js": "^3.1.47", 53 | "@types/d3-delaunay": "4.1.0", 54 | "@types/glob": "^7.1.2", 55 | "@types/inquirer": "6.5.0", 56 | "@types/node": "^14.0.13", 57 | "@types/proj4": "2.5.0", 58 | "@types/shapefile": "^0.6.0", 59 | "@types/table": "^5.0.0", 60 | "@types/tmp": "0.2.0", 61 | "@types/xmldom": "^0.1.29", 62 | "capture-console": "^1.0.1", 63 | "chai": "^4.2.0", 64 | "express": "^4.17.1", 65 | "mocha": "^9.1.3", 66 | "mock-require": "^3.0.3", 67 | "rewire": "^5.0.0", 68 | "ts-node": "^8.10.2", 69 | "typescript": "^3.9.5" 70 | }, 71 | "bin": { 72 | "here": "bin/here.js" 73 | }, 74 | "files": [ 75 | "bin/**/*" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /scripts/publish-npm-gha.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # 3 | # Copyright (C) 2019-2020 HERE Europe B.V. 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 | # SPDX-License-Identifier: Apache-2.0 18 | # License-Filename: LICENSE 19 | # 20 | # simple script for npm publishing 21 | # to be run from travis 22 | 23 | set -ex 24 | 25 | echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > ~/.npmrc 26 | 27 | # Check that tag and package.json versions match 28 | PACKAGE_VERSION=`node -p "require('./package.json').version"` 29 | TAG_NAME=`echo $GITHUB_REF | cut -d'/' -f 3` 30 | if [[ $PACKAGE_VERSION != $TAG_NAME ]]; then 31 | echo "Tag version does not match package.json version." 32 | exit 1 33 | fi 34 | 35 | # Install 36 | npm ci 37 | 38 | # Publish 39 | npm publish --access=public 40 | 41 | echo "Published Here CLI version $PACKAGE_VERSION to https://www.npmjs.com/ successfully!" 42 | -------------------------------------------------------------------------------- /src/api-error.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 - 2021 HERE Europe B.V. 3 | SPDX-License-Identifier: MIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * A custom API error to denote the message along with the status code 27 | * 28 | */ 29 | export class ApiError extends Error { 30 | statusCode: Number; 31 | /** 32 | * Constructs the ApiError class 33 | * @param {int} statusCode response status code from the api 34 | * @param {String} message response message from the api 35 | * @constructor 36 | */ 37 | 38 | constructor(statusCode:Number, message:string) { 39 | super(message); 40 | this.statusCode = statusCode; 41 | } 42 | } -------------------------------------------------------------------------------- /src/catalogUtil.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | import * as common from "./common"; 27 | import {RequestFactory, OlpClientSettings} from "@here/olp-sdk-core"; 28 | import {ConfigApi, RequestBuilder} from "@here/olp-sdk-dataservice-api"; 29 | import * as inquirer from "inquirer"; 30 | let requestBuilder: RequestBuilder; 31 | 32 | const layerConfirmationPrompt = (catalogHrn: string) => [ 33 | { 34 | type: 'confirm', 35 | name: 'layerConfirmation', 36 | message: 'Layer ID not provided. Do you want to use an existing layer from catalog ' + catalogHrn + '?', 37 | default: true 38 | } 39 | ]; 40 | 41 | const catLayerConfirmationPrompt = [ 42 | { 43 | type: 'confirm', 44 | name: 'catLayerConfirmation', 45 | message: 'Catalog HRN and Layer ID not provided. Do you want to use an existing layer?', 46 | default: true 47 | } 48 | ]; 49 | 50 | async function getConfigApiRequestBuilder(token: string = ''){ 51 | if(requestBuilder){ 52 | return requestBuilder; 53 | } 54 | if (!token) { 55 | token = await common.getWorkspaceToken(); 56 | } 57 | const olpClientSettings = new OlpClientSettings({ 58 | environment: "here", 59 | getToken: async () => token 60 | }); 61 | requestBuilder = await RequestFactory.create("config","v1",olpClientSettings); 62 | return requestBuilder; 63 | } 64 | 65 | export async function deleteLayer(catalogHrn: string, layerId: string, token: string = ''){ 66 | const requestBuilder = await getConfigApiRequestBuilder(token); 67 | const statusLink = await ConfigApi.deleteLayer(requestBuilder, {catalogHrn: catalogHrn,layerId: layerId}); 68 | 69 | if(statusLink.configToken) { 70 | const statusResponse = await waitForStatus(requestBuilder, statusLink.configToken); 71 | if(statusResponse && statusResponse.status) { 72 | console.log("Layer delete for '" + layerId + "' is completed with status " + statusResponse.status) 73 | } else { 74 | console.log("Layer '" + layerId + "' deleted successfully"); 75 | } 76 | } 77 | } 78 | 79 | export async function createInteractiveMapLayer(catalogHrn: string, options: any, token: string = ''){ 80 | let catalog = await getCatalogDetails(catalogHrn, token); 81 | const requestBuilder = await getConfigApiRequestBuilder(token); 82 | let layers: any[] = catalog.layers; 83 | let layer = getLayerObject(options); 84 | layers.push(layer); 85 | let updateCatalogConfig: ConfigApi.CreateCatalog = { 86 | id : catalog.id, 87 | description : catalog.description, 88 | name : catalog.name, 89 | notifications : catalog.notifications, 90 | replication : catalog.replication, 91 | summary: catalog.summary, 92 | tags: catalog.tags, 93 | layers: layers 94 | } 95 | 96 | const statusLink = await ConfigApi.updateCatalog(requestBuilder, {catalogHrn: catalogHrn,body: updateCatalogConfig}); 97 | 98 | //TODO - check the status link for result when its completed 99 | if(statusLink.configToken) { 100 | const statusResponse: any = await waitForStatus(requestBuilder, statusLink.configToken); 101 | if(statusResponse && statusResponse.status) { 102 | console.log("Layer creation for '" + options.id + "' is completed with status " + statusResponse.status) 103 | } else { 104 | console.log("Interactive Map layer '" + options.id + "' is created successfully"); 105 | } 106 | } 107 | } 108 | 109 | export async function updateInteractiveMapLayer(catalogHrn: string, layerId: string, options: any, token: string = ''){ 110 | const requestBuilder = await getConfigApiRequestBuilder(token); 111 | let layer = getLayerObject(options); 112 | 113 | const statusLink = await ConfigApi.patchLayer(requestBuilder, {catalogHrn: catalogHrn, layerId: layerId, body: layer}); 114 | 115 | //TODO - check the status link for result when its completed 116 | if(statusLink.configToken) { 117 | const statusResponse: any = await waitForStatus(requestBuilder, statusLink.configToken); 118 | if(statusResponse && statusResponse.status) { 119 | console.log("Layer update for '" + layerId + "' is completed with status " + statusResponse.status) 120 | } else { 121 | console.log("Interactive Map layer '" + layerId + "' is updated successfully"); 122 | } 123 | } 124 | } 125 | 126 | export function getLayerObject(options: any, layerType: string = "interactivemap"){ 127 | 128 | let layer = { 129 | id : options.id, 130 | name: options.layerName, 131 | summary: options.summary, 132 | description: options.message, 133 | layerType: layerType, 134 | interactiveMapProperties: { 135 | searchableProperties: options.searchableProperties ? options.searchableProperties.split(",") : [] 136 | }, 137 | tags: options.tags?.split(","), 138 | billingTags: options.billingTags?.split(",") 139 | //hrn: catalogHrn + ":" + options.id, 140 | }; 141 | return layer; 142 | } 143 | 144 | export async function getCatalogDetails(catalogHrn: string, token: string = ''): Promise{ 145 | const requestBuilder = await getConfigApiRequestBuilder(token); 146 | return await ConfigApi.getCatalog(requestBuilder, {catalogHrn: catalogHrn}); 147 | } 148 | 149 | export async function getCatalogs(vebose: boolean, token: string){ 150 | const requestBuilder = await getConfigApiRequestBuilder(token); 151 | const catalogListResult = await ConfigApi.getCatalogs(requestBuilder, 152 | {verbose:vebose});//TODO - use layer filtering once interactivemap is available in the layerType 153 | if(catalogListResult.results) { 154 | return catalogListResult.results.items; 155 | } else { 156 | return []; 157 | } 158 | } 159 | 160 | export async function createCatalog(options: any, layers: any[] = []){ 161 | const requestBuilder = await getConfigApiRequestBuilder(options.token); 162 | let createCatalogConfig: ConfigApi.CreateCatalog = { 163 | id : options.id, 164 | description : options.message, 165 | name : options.catalogName, 166 | summary: options.summary, 167 | tags: options.tags?.split(","), 168 | layers: layers 169 | } 170 | const statusLink = await ConfigApi.createCatalog(requestBuilder, {body:createCatalogConfig}); 171 | //console.log(statusLink); 172 | //TODO - check the status link for result when its completed 173 | if(statusLink.configToken) { 174 | const statusResponse: any = await waitForStatus(requestBuilder, statusLink.configToken); 175 | if(statusResponse && statusResponse.status) { 176 | console.log('Catalog creation for ' + createCatalogConfig.id + ' is completed with status ' + statusResponse.status) 177 | } else { 178 | console.log("Catalog '" + statusResponse.id + "' created with HRN " + statusResponse.hrn); 179 | } 180 | } 181 | 182 | } 183 | 184 | export async function validateCatalogAndLayer(catalogHrn: string, layerId: string, token: string = ''){ 185 | const catalog = await getCatalogDetails(catalogHrn, token);//this verifies if catalogHrn is correct and user has access to it 186 | if(layerId){ 187 | for(const layer of (catalog.layers as Array)){ 188 | if(layer.id == layerId){ 189 | if(layer.layerType == "interactivemap"){ 190 | return catalog; 191 | } else { 192 | console.error("Error - layer " + layerId + " is not of type interactivemap"); 193 | process.exit(1); 194 | } 195 | } 196 | } 197 | console.error("Error - layer " + layerId + " is not present in the catalog " + catalogHrn); 198 | process.exit(1); 199 | } 200 | return catalog; 201 | } 202 | 203 | 204 | async function waitForStatus(builder: RequestBuilder, configToken: string) { 205 | let status = 'pending', response; 206 | while(status === 'pending') { 207 | await new Promise(done => setTimeout(done, 500)); 208 | const statusResponse = await ConfigApi.getCatalogStatus(builder, {token: configToken}); 209 | status = statusResponse.status ? statusResponse.status : 'success'; 210 | response = statusResponse; 211 | } 212 | return response; 213 | } 214 | 215 | export async function catalogLayerSelectionPrompt(catalogHrn: string, layerId: string, options: any) { 216 | let catLayer: {catalogHrn: string, layerId: string, catalog: ConfigApi.Catalog|undefined} = { 217 | catalogHrn: catalogHrn, 218 | layerId: layerId, 219 | catalog: undefined 220 | }; 221 | 222 | if(catalogHrn) { 223 | const catalog = await validateCatalogAndLayer(catalogHrn, layerId); 224 | catLayer.catalog = catalog; 225 | if(!layerId) { 226 | const hasIml = catalog.layers.some((layer: any) => layer.layerType === 'interactivemap'); 227 | if(!hasIml) { 228 | console.error("Error - No Interactive Map layer is present in the catalog " + catalogHrn); 229 | process.exit(1); 230 | } else { 231 | const layerInput = await inquirer.prompt<{ layerConfirmation?: boolean }>(layerConfirmationPrompt(catalogHrn)); 232 | if(layerInput.layerConfirmation) { 233 | let layerChoiceList : { name: string, value: string }[] = []; 234 | catalog.layers.forEach((layer: any) => { 235 | if(layer.layerType === 'interactivemap') { 236 | layerChoiceList.push({ 237 | name: layer.hrn + " - " + layer.name, 238 | value : layer.id + "" 239 | }); 240 | } 241 | }); 242 | 243 | const layerQuestion = [ 244 | { 245 | type: "list", 246 | name: "layerId", 247 | message: "Please select the layer", 248 | choices: layerChoiceList 249 | } 250 | ]; 251 | catLayer.layerId = (await inquirer.prompt<{layerId:string}>(layerQuestion)).layerId; 252 | } 253 | } 254 | } 255 | } else { 256 | const catLayerInput = await inquirer.prompt<{ catLayerConfirmation?: boolean }>(catLayerConfirmationPrompt); 257 | if(catLayerInput.catLayerConfirmation) { 258 | let catalogs = await getCatalogs(true,options.token); 259 | catalogs = (catalogs as ConfigApi.Catalog[])?.filter(catalog => catalog.layers.some((layer: any) => layer.layerType === 'interactivemap')) 260 | if (!catalogs || catalogs.length == 0) { 261 | console.log("No layers found"); 262 | } else { 263 | let layerChoiceList : { name: string, value: any }[] = []; 264 | catalogs.forEach((catalog: any) => { 265 | catalog.layers.forEach((layer: any) => { 266 | if(layer.layerType === 'interactivemap') { 267 | layerChoiceList.push({ 268 | name: layer.hrn + " - " + layer.name, 269 | value : {catalogHrn: catalog.hrn, layerId: layer.id, catalog: catalog} 270 | }); 271 | } 272 | }); 273 | }); 274 | 275 | const layerQuestion = [ 276 | { 277 | type: "list", 278 | name: "catLayer", 279 | message: "Please select the layer", 280 | choices: layerChoiceList 281 | } 282 | ]; 283 | catLayer = (await inquirer.prompt<{catLayer:any}>(layerQuestion)).catLayer; 284 | } 285 | } 286 | } 287 | return catLayer; 288 | } -------------------------------------------------------------------------------- /src/geocodeUtil.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | import * as common from "./common"; 28 | import { requestAsync } from "./requestAsync"; 29 | 30 | export async function geoCodeString(locationString: string, hasAppCode: any, appInfo: any = null) { 31 | 32 | if (!appInfo) { 33 | let dataStr = await common.decryptAndGet("appDetails"); 34 | appInfo = common.getSplittedKeys(dataStr); 35 | if (!appInfo) { 36 | throw new Error("Account information out of date. Please re-run 'here configure'."); 37 | } 38 | } 39 | 40 | const appId = appInfo[0]; 41 | let response = await execGeoCode(locationString, hasAppCode, appInfo); 42 | if (response.statusCode !== 200) { 43 | if(response.statusCode === 401 && !hasAppCode) { 44 | await common.encryptAndStore("apiKeys", appId); 45 | response = await execGeoCode(locationString, hasAppCode, appInfo); 46 | if(response.statusCode !== 200) { 47 | if(response.statusCode === 401) { 48 | console.log("API Keys for AppId "+ appId +" is disabled or not generated. \n" + 49 | "Please generate/enable your API Keys at https://developer.here.com." + 50 | "If already generated/enabled, please try again in a few minutes."); 51 | process.exit(1); 52 | } else { 53 | throw new Error(response.body); 54 | } 55 | } 56 | } else if(response.statusCode === 403) { 57 | console.log("Invalid credentials for AppId "+appId+". Please re-run 'here configure'."); 58 | process.exit(1); 59 | } else { 60 | throw new Error(response.body); 61 | } 62 | } 63 | return response; 64 | } 65 | 66 | async function execGeoCode(locationString: string, hasAppCode: any, appInfo: any) { 67 | 68 | const appId = appInfo[0]; 69 | if(hasAppCode === false) { 70 | 71 | let apiKeys = await common.decryptAndGet("apiKeys"); 72 | appInfo = common.getSplittedKeys(apiKeys); 73 | 74 | if(!appInfo) { 75 | const accountInfo:string = await common.decryptAndGet("accountInfo","Please run `here configure` command."); 76 | const credentials = accountInfo.split("%%"); 77 | const cookieData = await common.hereAccountLogin(credentials[0], credentials[1]); 78 | apiKeys = await common.getApiKeys(cookieData, appId).catch(err => {throw err}); 79 | await common.encryptAndStore('apiKeys', apiKeys).catch(err => {throw err}); 80 | appInfo = common.getSplittedKeys(apiKeys); 81 | } 82 | } 83 | 84 | if (!appInfo) { 85 | console.log("API Keys for AppId "+ appId +" is disabled or not generated. \n" + 86 | "Please generate/enable your API Keys at https://developer.here.com."); 87 | process.exit(1); 88 | } 89 | let geocodeURL; 90 | if(hasAppCode) { 91 | geocodeURL = 'https://geocoder.api.here.com/6.2/geocode.json' + 92 | '?app_id=' + encodeURIComponent(appInfo[0]) + 93 | '&app_code=' + encodeURIComponent(appInfo[1]) + 94 | '&searchtext=' + encodeURIComponent(locationString); 95 | } else { 96 | geocodeURL = 'https://geocoder.ls.hereapi.com/6.2/geocode.json' + 97 | '?apiKey=' + encodeURIComponent(appInfo[1]) + 98 | '&searchtext=' + encodeURIComponent(locationString); 99 | } 100 | return await requestAsync({ url: geocodeURL }); 101 | } -------------------------------------------------------------------------------- /src/gisUtil.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 - 2021 HERE Europe B.V. 3 | SPDX-License-Identifier: MIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | import * as common from "./common"; 26 | import * as xyz from "./here-xyz"; 27 | import * as tmp from "tmp"; 28 | import * as fs from "fs"; 29 | import * as turf from "@turf/turf"; 30 | import {Delaunay} from "d3-delaunay"; 31 | import {getSpaceDataFromXyz, uploadToXyzSpace, getSpaceStatistics} from "./xyzutil"; 32 | 33 | export async function performGisOperation(id:string, options:any){ 34 | await common.verifyProLicense(); 35 | const sourceId = id; 36 | options.totalRecords = Number.MAX_SAFE_INTEGER; 37 | options.currentHandleOnly = true; 38 | options.handle = 0; 39 | if(options.chunk){ 40 | options.limit = options.chunk; 41 | } else { 42 | options.limit = 20; 43 | } 44 | if(!options['length'] && !options.centroid && !options.area && !options.voronoi && !options.delaunay){ 45 | console.log("Please specify GIS operation option"); 46 | process.exit(1); 47 | } 48 | if(options.neighbours && !options.delaunay){ 49 | console.log("Error: --neighbours option is only valid --delaunay option"); 50 | process.exit(1); 51 | } 52 | let cHandle; 53 | let gisFeatures : any[]= []; 54 | let featureCount = 0; 55 | let isValidGisFeatures = false; 56 | let delaunayFeaturesMap = new Map(); 57 | console.log("Performing GIS operation on the space data"); 58 | let tmpObj = tmp.fileSync({ mode: 0o644, prefix: 'gis', postfix: (options.voronoi || options.delaunay) ? '.geojson':'.geojsonl' }); 59 | do { 60 | let jsonOut = await getSpaceDataFromXyz(id, options); 61 | if (jsonOut.features && jsonOut.features.length === 0 && options.handle == 0) { 62 | console.log("\nNo features are available to execute GIS operation"); 63 | process.exit(1); 64 | } 65 | cHandle = jsonOut.handle; 66 | options.handle = jsonOut.handle; 67 | if (jsonOut.features) { 68 | const features = jsonOut.features; 69 | features.forEach(function (feature: any, i: number){ 70 | if(options.voronoi || options.delaunay){ 71 | if(feature.geometry && (feature.geometry.type == 'Point')){ 72 | if(options.delaunay){ 73 | delaunayFeaturesMap.set(feature.geometry.coordinates.slice(0,2).toString(), feature); 74 | } else { 75 | gisFeatures.push(feature); 76 | } 77 | } 78 | } else { 79 | let gisFeature = performTurfOperationOnFeature(feature, options); 80 | if(gisFeature){ 81 | gisFeatures.push(gisFeature); 82 | process.stdout.write("\rGIS operation done for feature count - " + (featureCount + (i+1))); 83 | } 84 | } 85 | }); 86 | featureCount += features.length; 87 | if(gisFeatures.length > 0 && !(options.voronoi || options.delaunay)){ 88 | isValidGisFeatures = true; 89 | fs.appendFileSync(tmpObj.name, JSON.stringify({ type: "FeatureCollection", features: gisFeatures }) + '\n'); 90 | gisFeatures = []; 91 | } 92 | } else { 93 | cHandle = -1; 94 | } 95 | } while (cHandle >= 0); 96 | process.stdout.write("\n"); 97 | if(options.delaunay){ 98 | gisFeatures = Array.from(delaunayFeaturesMap.values()); 99 | } 100 | if(gisFeatures.length == 0 && !isValidGisFeatures){ 101 | console.log("required geometry features are not available to perform GIS operation"); 102 | process.exit(1); 103 | } 104 | 105 | if(options.voronoi){ 106 | console.log("Calculating Voronoi Polygons for points data"); 107 | gisFeatures = await calculateVoronoiPolygon(id, gisFeatures, options); 108 | fs.writeFileSync(tmpObj.name, JSON.stringify({ type: "FeatureCollection", features: gisFeatures })); 109 | } else if(options.delaunay){ 110 | console.log("Performing delaunay operation on " + gisFeatures.length + " features"); 111 | const delaunayFeatures = calculateDelaunayTriangles(gisFeatures, delaunayFeaturesMap, options.neighbours); 112 | fs.writeFileSync(tmpObj.name, JSON.stringify({ type: "FeatureCollection", features: delaunayFeatures })); 113 | if(options.neighbours){ 114 | let neighboursTmpObj = tmp.fileSync({ mode: 0o644, prefix: 'gis', postfix: '.geojson'}); 115 | fs.writeFileSync(neighboursTmpObj.name, JSON.stringify({ type: "FeatureCollection", features: gisFeatures })); 116 | options.file = neighboursTmpObj.name; 117 | options.stream = true; 118 | console.log("updating Delaunay neighbours"); 119 | await uploadToXyzSpace(id, options); 120 | } 121 | } 122 | if(!options.samespace && (options.centroid || options.voronoi || options.delaunay)){ 123 | let newSpaceData; 124 | if(options.centroid){ 125 | newSpaceData = await xyz.createNewSpaceAndUpdateMetadata('centroid', sourceId, options); 126 | } else if(options.voronoi){ 127 | newSpaceData = await xyz.createNewSpaceAndUpdateMetadata('voronoi', sourceId, options); 128 | } else if(options.delaunay){ 129 | newSpaceData = await xyz.createNewSpaceAndUpdateMetadata('delaunay', sourceId, options); 130 | } 131 | id = newSpaceData.id; 132 | } 133 | 134 | if (gisFeatures.length > 0 || isValidGisFeatures) { 135 | if(options.centroid){ 136 | options.tags = 'centroid'; 137 | } else if(options.voronoi){ 138 | options.tags = 'voronoi'; 139 | } else if(options.delaunay){ 140 | options.tags = 'delaunay'; 141 | } 142 | options.file = tmpObj.name; 143 | options.stream = true; 144 | await uploadToXyzSpace(id, options); 145 | console.log("GIS operation completed on space " + sourceId); 146 | } 147 | } 148 | 149 | function performTurfOperationOnFeature(feature: any, options: any){ 150 | let gisFeature; 151 | if(options.centroid){ 152 | if(feature.geometry && (feature.geometry.type == 'LineString' || feature.geometry.type == 'Polygon' || feature.geometry.type == 'MultiLineString' || feature.geometry.type == 'MultiPolygon')){ 153 | gisFeature = turf.centroid(feature, feature.properties); 154 | if(options.samespace){ 155 | if(!gisFeature.properties){ 156 | gisFeature.properties = {}; 157 | } 158 | gisFeature.properties.sourceId = feature.id; 159 | } else { 160 | gisFeature.id = feature.id; 161 | } 162 | } 163 | } else if(options['length']){ 164 | if(feature.geometry && (feature.geometry.type == 'LineString' || feature.geometry.type == 'MultiLineString')){ 165 | let length = turf.length(feature, {units: 'meters'}); 166 | if(!feature.properties){ 167 | feature.properties = {}; 168 | } 169 | feature.properties['xyz_length_m'] = length; 170 | feature.properties['xyz_length_km'] = parseFloat((length / 1000).toFixed(2)); 171 | feature.properties['xyz_length_miles'] = parseFloat((length * 0.000621371).toFixed(2)); 172 | gisFeature = feature; 173 | } 174 | } else if(options.area){ 175 | if(feature.geometry && (feature.geometry.type == 'Polygon' || feature.geometry.type == 'MultiPolygon')){ 176 | gisFeature = populateArea(feature); 177 | } 178 | } else { 179 | console.log("Please specify GIS operation option"); 180 | process.exit(1); 181 | } 182 | return gisFeature; 183 | } 184 | 185 | function populateArea(feature: any){ 186 | let area = turf.area(feature); 187 | if(!feature.properties){ 188 | feature.properties = {}; 189 | } 190 | feature.properties['xyz_area_sqm'] = area; 191 | feature.properties['xyz_area_sqkm'] = parseFloat((area / 1000000).toFixed(2)); 192 | feature.properties['xyz_area_sqmiles'] = parseFloat((area * 0.00000038610215855).toFixed(2)); 193 | return feature; 194 | } 195 | 196 | async function calculateVoronoiPolygon(spaceId: string, features: any[], options: any){ 197 | const statData = await getSpaceStatistics(spaceId); 198 | const bbox: [number,number,number, number] = statData.bbox.value; 199 | const delaunay = Delaunay.from(features, function(feature){return feature.geometry.coordinates[0]}, function(feature){return feature.geometry.coordinates[1]}); 200 | const voronoiResult = delaunay.voronoi(bbox).cellPolygons(); 201 | let result = voronoiResult.next(); 202 | let i = 0; 203 | let voronoiFeatures = []; 204 | while (!result.done) { 205 | //console.log(JSON.stringify(result.value)); 206 | let polygon = turf.polygon([result.value],features[i].properties); 207 | if(options.samespace){ 208 | if(!polygon.properties){ 209 | polygon.properties = {}; 210 | } 211 | polygon.properties.sourceId = features[i].id; 212 | } else { 213 | polygon.id = features[i].id; 214 | } 215 | polygon = populateArea(polygon); 216 | voronoiFeatures.push(polygon); 217 | result = voronoiResult.next(); 218 | i++; 219 | } 220 | return voronoiFeatures; 221 | } 222 | 223 | function calculateDelaunayTriangles(features: any[], delaunayFeaturesMap: Map, calculateNeighbours: boolean){ 224 | const delaunay = Delaunay.from(features, function(feature){return feature.geometry.coordinates[0]}, function(feature){return feature.geometry.coordinates[1]}); 225 | const delaunayResult = delaunay.trianglePolygons(); 226 | let result = delaunayResult.next(); 227 | let delaunayFeatures = []; 228 | while (!result.done) { 229 | //console.log(JSON.stringify(result.value)); 230 | let properties = { 231 | a : delaunayFeaturesMap.get(result.value[0].toString()).id, 232 | b : delaunayFeaturesMap.get(result.value[1].toString()).id, 233 | c : delaunayFeaturesMap.get(result.value[2].toString()).id, 234 | } 235 | let polygon = turf.polygon([result.value], properties); 236 | polygon = populateArea(polygon); 237 | delaunayFeatures.push(polygon); 238 | result = delaunayResult.next(); 239 | } 240 | if(calculateNeighbours){ 241 | for(let i=0; i < features.length; i++){ 242 | const neighbours = delaunay.neighbors(i); 243 | let neighbourResult = neighbours.next(); 244 | let neighbourIds: string[] = []; 245 | while (!neighbourResult.done) { 246 | neighbourIds.push(features[neighbourResult.value].id); 247 | neighbourResult = neighbours.next(); 248 | } 249 | features[i].properties['xyz_delaunay_neighbours'] = neighbourIds; 250 | } 251 | } 252 | return delaunayFeatures; 253 | /* 254 | const featureCollection = { type: "FeatureCollection", features: features }; 255 | const tinFeatureCollection = turf.tin(featureCollection, property); 256 | tinFeatureCollection.features.forEach(function (polygon, i) { 257 | polygon = populateArea(polygon); 258 | }); 259 | return tinFeatureCollection.features; 260 | */ 261 | } 262 | -------------------------------------------------------------------------------- /src/here-catalog.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | import * as program from "commander"; 27 | import * as common from "./common"; 28 | import * as catalogUtil from "./catalogUtil"; 29 | import {ConfigApi} from "@here/olp-sdk-dataservice-api"; 30 | 31 | program.version("0.1.0"); 32 | 33 | program 34 | .command("create") 35 | .description("create a new catalog") 36 | .requiredOption("-i, --id ", "Id for catalog") 37 | .requiredOption("-n, --catalogName ", "Name for catalog") 38 | .option("-s, --summary

", "Short summary") 39 | .option("-m, --message ", "Description for catalog") 40 | .option("--tags ", "a comma separated list of tags") 41 | .option("--token ", "a external token to create layer in other user's account") 42 | .action(async function (options) { 43 | if (!options.summary) { 44 | options.summary = "a new catalog created from commandline"; 45 | } 46 | if (!options.message) { 47 | options.message = "a new catalog created from commandline"; 48 | } 49 | catalogUtil.createCatalog(options) 50 | .catch(error => { 51 | common.handleError(error); 52 | }); 53 | }); 54 | 55 | program 56 | .command("list") 57 | .alias("ls") 58 | .description("information about available catalogs") 59 | .option("-r, --raw", "show raw catalog definition") 60 | .option("--filter ", "a comma separted strings to filter catalogs") 61 | .option("--token ", "a external token to access another user's catalogs") 62 | .action(async function (options) { 63 | try{ 64 | let catalogs = await catalogUtil.getCatalogs(false,options.token); 65 | if (!catalogs || catalogs.length == 0) { 66 | console.log("No catalogs found"); 67 | } else { 68 | if(options.filter){ 69 | const filterArray = options.filter.split(","); 70 | catalogs = (catalogs as Array).filter((element: ConfigApi.CatalogSummary) => { 71 | for (var i=0; i true 55 | }], function (err: any, result: any) { 56 | common.login(result['AppId'], result['AppCode']).catch(err => console.error(err)); 57 | }); 58 | } 59 | */ 60 | 61 | program 62 | .command('account') 63 | .description('configure HERE account email/password for authentiction. Account can be created from https://developer.here.com/') 64 | .action(function (options) { 65 | setUserPass(); 66 | }); 67 | 68 | async function setUserPass() { 69 | prompter.start(); 70 | prompter.get([{ 71 | name: 'Email', 72 | required: true 73 | }, { 74 | name: 'Password', 75 | hidden: true, 76 | conform: () => true 77 | }], async function (err: any, result: any) { 78 | await common.loginFlow(result['Email'], result['Password']); 79 | }); 80 | } 81 | 82 | program 83 | .command('verify') 84 | //.arguments('[env]') 85 | .description('verify credentials') 86 | .action(function (env, options) { 87 | common.verify(); 88 | }); 89 | 90 | 91 | program 92 | .command('refresh') 93 | .description('refresh account setup') 94 | .action(function (options:any) { 95 | common.refreshAccount(true); 96 | }); 97 | 98 | program 99 | .command('server ') 100 | .description('set other apiServer Url for e.g. http://localhost:8080') 101 | .action(function (apiServerUrl:string, options:any) { 102 | common.setApiServerUrl(apiServerUrl); 103 | }); 104 | 105 | program 106 | .command('workspace ') 107 | .description('here workspace credentials file path, by default it will be checked in the home directory') 108 | .action(function (credentialsFilePath:string, options:any) { 109 | common.setWorkSpaceCredentials(credentialsFilePath); 110 | }); 111 | 112 | prompter.stop(); 113 | //program.parse(process.argv); 114 | 115 | if (process.argv.length == 2) { 116 | setUserPass(); 117 | } else { 118 | common.validate(["help","verify","account","refresh","server","workspace"], [process.argv[2]], program); 119 | program.parse(process.argv); 120 | } 121 | 122 | process.on('uncaughtException', error => { 123 | console.log(error.message); 124 | }); -------------------------------------------------------------------------------- /src/here-geocode.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 - 2021 HERE Europe B.V. 3 | SPDX-License-Identifier: MIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | import * as program from "commander"; 26 | import * as common from "./common"; 27 | import { geoCodeString } from "./geocodeUtil"; 28 | 29 | program 30 | .name('here geocode') 31 | .version('0.1.0') 32 | .parse(process.argv); 33 | geoCode(process.argv[2]).catch(err => console.error(err)); 34 | 35 | function toFeature(result: any) { 36 | return { 37 | "type": "Feature", 38 | "id": result.Location.LocationId, 39 | "geometry": { 40 | "type": "Point", 41 | "coordinates": [result.Location.DisplayPosition.Longitude, result.Location.DisplayPosition.Latitude] 42 | }, 43 | "properties": result 44 | } 45 | } 46 | 47 | async function geoCode(locationString: string) { 48 | 49 | let hasAppCode = true; 50 | let dataStr = await common.decryptAndGet("appDetails"); 51 | let appInfo = common.getSplittedKeys(dataStr); 52 | 53 | if (!appInfo) { 54 | throw new Error("Account information out of date. Please re-run 'here configure'."); 55 | } 56 | 57 | const appId = appInfo[0]; 58 | const appCode = appInfo[1]; 59 | 60 | if(!appCode || appCode === 'undefined') { 61 | hasAppCode = false; 62 | } 63 | 64 | let response = await geoCodeString(locationString, hasAppCode, appInfo); 65 | 66 | let geocodeJson = JSON.parse(response.body); 67 | if (geocodeJson.Response.View.length == 0) { 68 | console.log("Could not geocode the place '" + locationString + "'"); 69 | } else { 70 | console.log(JSON.stringify(toGeoJson(geocodeJson), null, 2)); 71 | } 72 | } 73 | 74 | function toGeoJson(responseJson: any) { 75 | const features = new Array(); 76 | responseJson.Response.View[0].Result.forEach((element: any) => { 77 | features.push(toFeature(element)); 78 | }); 79 | return { 80 | "type": "FeatureCollection", 81 | "features": features 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /src/here-interactivemap.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | import * as program from "commander"; 27 | import * as common from "./common"; 28 | import * as xyzutil from "./xyzutil"; 29 | import * as catalogUtil from "./catalogUtil"; 30 | import * as inquirer from "inquirer"; 31 | import {ConfigApi} from "@here/olp-sdk-dataservice-api"; 32 | 33 | const catalogConfirmationPrompt = [ 34 | { 35 | type: 'confirm', 36 | name: 'catalogConfirmation', 37 | message: 'Do you want to use existing catalog?', 38 | default: true 39 | } 40 | ]; 41 | 42 | const newCatalogCreationPrompt = [ 43 | { 44 | type: 'input', 45 | name: 'id', 46 | message: 'Enter an id for the new catalog: ' 47 | }, 48 | { 49 | type: 'input', 50 | name: 'catalogName', 51 | message: 'Enter a Name for the new catalog: ' 52 | }, 53 | { 54 | type: 'input', 55 | name: 'summary', 56 | message: 'Enter a summary for the new catalog: ' 57 | }, 58 | { 59 | type: 'input', 60 | name: 'message', 61 | message: 'Enter a description for the new catalog: ' 62 | } 63 | ]; 64 | 65 | program.version("0.1.0"); 66 | 67 | program 68 | .command("create [catalogHrn]") 69 | .description("create a new interactive map layer") 70 | .requiredOption("-i, --id ", "Id for interactive map layer") 71 | .requiredOption("-n, --layerName ", "Name for interactive map layer") 72 | .option("-s, --summary ", "Short summary") 73 | .option("-m, --message ", "Description for interactive map layer") 74 | .option("-p, --searchableProperties ", "a comma separated list of properties to be indexed for faster queries") 75 | .option("--tags ", "a comma separated list of tags") 76 | .option("--billingTags ", "a comma separated list of billing tags") 77 | .option("--token ", "a external token to create layer in other user's account") 78 | .action(async function (catalogHrn, options) { 79 | if (!options.summary) { 80 | options.summary = "a new interactive map layer created from commandline"; 81 | } 82 | if (!options.message) { 83 | options.message = "a new interactive map layer created from commandline"; 84 | } 85 | try { 86 | if(!catalogHrn){ 87 | const catalogInput = await inquirer.prompt<{ catalogConfirmation?: boolean }>(catalogConfirmationPrompt); 88 | if(!catalogInput.catalogConfirmation){ 89 | let catalogOptions = await inquirer.prompt(newCatalogCreationPrompt); 90 | catalogOptions.token = options.token; 91 | const layer = catalogUtil.getLayerObject(options); 92 | await catalogUtil.createCatalog(catalogOptions, [layer]); 93 | } else { 94 | let catalogs = await catalogUtil.getCatalogs(false,options.token); 95 | let catalogChoiceList : { name: string, value: string }[] = []; 96 | if(catalogs){ 97 | for(let catalog of (catalogs as Array)){ 98 | catalogChoiceList.push({ 99 | name: catalog.title + " - " + catalog.hrn, 100 | value : catalog.hrn + "" 101 | }) 102 | } 103 | } 104 | const catalogQuestion = [ 105 | { 106 | type: "list", 107 | name: "catalogHrn", 108 | message: "Please select the catalog", 109 | choices: catalogChoiceList 110 | } 111 | ]; 112 | catalogHrn = (await inquirer.prompt<{catalogHrn:string}>(catalogQuestion)).catalogHrn; 113 | } 114 | } 115 | if(catalogHrn){ 116 | await catalogUtil.createInteractiveMapLayer(catalogHrn, options, options.token); 117 | } 118 | } catch(error){ 119 | common.handleError(error); 120 | }; 121 | }); 122 | 123 | 124 | program 125 | .command("config [catalogHrn] [layerId]") 126 | .description("configure/update an interactive map layer in a catalog") 127 | .option("-n, --layerName ", "Name for interactive map layer") 128 | .option("-s, --summary ", "Short summary") 129 | .option("-m, --message ", "Description for interactive map layer") 130 | .option("-p, --searchableProperties ", "a comma separated list of properties to be indexed for faster queries") 131 | .option("--tags ", "a comma separated list of tags") 132 | .option("--billingTags ", "a comma separated list of billing tags") 133 | .option("--token ", "a external token to create layer in other user's account") 134 | .action(async function (catalogHrn, layerId, options) { 135 | try { 136 | const catLayer = await catalogUtil.catalogLayerSelectionPrompt(catalogHrn, layerId, options); 137 | 138 | if(catLayer.catalogHrn && catLayer.layerId){ 139 | let layer: any = catLayer.catalog?.layers.find(x => x.id === catLayer.layerId); 140 | if(!options.summary) { 141 | options.summary = layer.summary; 142 | } 143 | await catalogUtil.updateInteractiveMapLayer(catLayer.catalogHrn, catLayer.layerId, options, options.token); 144 | } 145 | } catch(error){ 146 | common.handleError(error); 147 | }; 148 | }); 149 | 150 | 151 | program 152 | .command("upload [catalogHrn] [layerId]") 153 | .description("upload one or more GeoJSON, CSV, GPX, XLS, or a Shapefile to the given layerid. GeoJSON feature IDs will be respected unless you override with -o or specify with -i; pipe GeoJSON via stdout using | here iml upload ") 154 | .option("-f, --file ", "comma separated list of local GeoJSON, GeoJSONL, Shapefile, CSV, GPX, or XLS files (or GeoJSON/CSV URLs); use a directory path and --batch [filetype] to upload all files of that type within a directory") 155 | .option("-c, --chunk [chunk]", "chunk size, default 200 -- use smaller values (1 to 10) to allow safer uploads of very large geometries (big polygons, many properties), use higher values (e.g. 500 to 5000) for faster uploads of small geometries (points and lines, few properties)") 156 | .option("--token ", "a external token to upload data to another user's layer") 157 | .option("-x, --lon [lon]", "longitude field name") 158 | .option("-y, --lat [lat]", "latitude field name") 159 | .option("-z, --point [point]", "points field name with coordinates like (Latitude,Longitude) e.g. (37.7,-122.4)") 160 | .option("--lonlat", "parse a -—point/-z csv field as (lon,lat) instead of (lat,lon)") 161 | .option("-i, --id [id]", "property name(s) to be used as the feature ID (must be unique) -- multiple values can be comma separated") 162 | // TODO: Either remove --assign option or modify assign to remove tags - Removing for now 163 | //.option("-a, --assign","interactive mode to analyze and select fields to be used as tags and unique feature IDs") 164 | .option("-o, --override", "override default feature ID and use property hash feature ID generation") 165 | .option("-s, --stream", "streaming support for upload and/or large csv and geojson uploads using concurrent writes, tune chunk size with -c") 166 | .option('-d, --delimiter [,]', 'alternate delimiter used in CSV', ',') 167 | .option('-q, --quote ["]', 'quote used in CSV', '"') 168 | .option('-e, --errors', 'print data upload errors') 169 | .option('--string-fields ', 'property name(s) of CSV string fields *not* to be automatically converted into numbers or booleans (e.g. number-like census geoids, postal codes with leading zeros)') 170 | .option('--groupby ', 'consolidate multiple rows of a CSV into a single feature based on a unique ID designated with -i; values of each row within the selected column will become top level properties within the consolidated feature') 171 | .option('--promote ', 'comma separated column names which should not be nested within a top level property generated consolidated by --groupby') 172 | .option('--flatten', 'stores the --groupby consolidated output in flattened string separated by colon (:) instead of a nested object') 173 | .option('--date ', 'date-related property name(s) of a feature to be normalized as a ISO 8601 datestring (datahub_iso8601_[propertyname]), and unix timestamp (datahub_timestamp_[propertyname] ') 174 | .option('--dateprops [datepropsString]', 'comma separated list of granular date properties to be added via --date. possible options - year, month, week, weekday, year_month, year_week, hour') 175 | .option('--noCoords', 'upload CSV files with no coordinates, generates null geometry and tagged with null_island (best used with -i)') 176 | .option('--batch [batch]', 'upload all files of the same type within a directory; specify "--batch [geojson|geojsonl|csv|shp|gpx|xls]" (will inspect shapefile subdirectories); select directory with -f') 177 | .action(async function (catalogHrn, layerId, options) { 178 | try { 179 | xyzutil.validateUploadOptions(options); 180 | const catLayer = await catalogUtil.catalogLayerSelectionPrompt(catalogHrn, layerId, options); 181 | if(catLayer.catalogHrn && catLayer.layerId) { 182 | xyzutil.setCatalogHrn(catLayer.catalogHrn); 183 | await xyzutil.uploadToXyzSpace(catLayer.layerId, options); 184 | } 185 | } catch(error) { 186 | common.handleError(error, true); 187 | } 188 | 189 | }); 190 | 191 | program 192 | .command("list") 193 | .alias("ls") 194 | .description("information about available interactive map layers") 195 | .option("-r, --raw", "show raw layer definition") 196 | .option("--token ", "a external token to access another user's layers") 197 | .option("--filter ", "a comma separted strings to filter layers") 198 | .action(async function (options) { 199 | try { 200 | let catalogs = await catalogUtil.getCatalogs(true,options.token); 201 | if (!catalogs || catalogs.length == 0) { 202 | console.log("No layers found"); 203 | } else { 204 | let layers: any[] = []; 205 | for(let catalog of (catalogs as Array)){ 206 | layers = layers.concat(catalog.layers.filter((element: any) => {//TODO - change it to ConfigApi.Layer once layerType issue is resolved 207 | if(element.layerType == "interactivemap"){ 208 | if(options.filter){ 209 | const filterArray = options.filter.split(","); 210 | for (var i=0; i ") 236 | .description("shows the content of the given id") 237 | .option("-l, --limit ", "Number of objects to be fetched") 238 | .option("-o, --offset ", "The offset / handle to continue the iteration") 239 | //.option("-t, --tags ", "Tags to filter on") 240 | .option("-r, --raw", "show raw interactive map layer content") 241 | .option("--all", "iterate over entire interactive map layer to get entire data of layer, output will be shown on the console in GeoJSON format") 242 | .option("--geojsonl", "to print output of --all in geojsonl format") 243 | .option("-c, --chunk [chunk]", "chunk size to use in --all option, default 5000") 244 | .option("--token ", "a external token to access another user's layer") 245 | .option("-p, --prop ", "selection of properties, use p. or f.") 246 | //.option("-w, --web", "display Data Hub space on http://geojson.tools") 247 | //.option("-v, --vector", "inspect and analyze using Data Hub Space Invader and tangram.js") 248 | //.option("-x, --permanent", "uses Permanent token for --web and --vector option") 249 | .option("-s, --search ", "search expression in \"double quotes\", use single quote to signify string value, use p. or f. (Use '+' for AND , Operators : >,<,<=,<=,=,!=) (use comma separated values to search multiple values of a property) {e.g. \"p.name=John,Tom+p.age<50+p.phone='9999999'+p.zipcode=123456\"}") 250 | .option("--spatial","perform a spatial search on a layer using --center, --feature, or --geometry") 251 | //.option("--h3

","h3 resolution level to be used to iterate through a large spatial search on a space") 252 | //.option("--saveHexbins","save the h3 hexbin geometries used to iterate through a large spatial layer on a space") 253 | //.option("--targetSpace [targetSpace]","target space id where the results of h3 spatial search will be written") 254 | .option("--radius ", "the radius to be used with a --spatial --center search, or to add a buffer to a line or polygon (in meters)") 255 | .option("--center
", "comma separated, double-quoted lon,lat values specifying the center point of a --radius search") 256 | .option("--feature ", "comma separated 'catalogHrn,layerId,featureid' values specifying a reference geometry in another layer for a spatial query") 257 | .option("--geometry ", "geometry file to be uploaded for a --spatial query (a single feature in geojson file)") 258 | .action(async function (catalogHrn, layerId, options) { 259 | try { 260 | xyzutil.validateShowOptions(options); 261 | await catalogUtil.validateCatalogAndLayer(catalogHrn, layerId);//validate catalogHrn and layerId 262 | xyzutil.setCatalogHrn(catalogHrn); 263 | await xyzutil.showSpace(layerId, options) 264 | } catch(error) { 265 | common.handleError(error); 266 | } 267 | }); 268 | 269 | program 270 | .command("delete ") 271 | .description("delete the interactive map layer with the given id") 272 | .option("--force", "skip the confirmation prompt") 273 | .option("--token ", "a external token to delete another user's layer") 274 | .action(async (catalogHrn, layerId, options) => { 275 | try { 276 | const catalog = await catalogUtil.validateCatalogAndLayer(catalogHrn, layerId);//validate catalogHrn and layerId 277 | const layer = catalog.layers.find(layer => layer.id === layerId); 278 | 279 | xyzutil.setCatalogHrn(catalogHrn); 280 | xyzutil.setLayer(layer); 281 | await xyzutil.deleteSpace(layerId, options); 282 | } catch(error) { 283 | common.handleError(error); 284 | } 285 | }); 286 | 287 | program 288 | .command("clear ") 289 | .description("clear data from interactive map layer") 290 | //.option("-t, --tags ", "tags for the Data Hub space") 291 | .option("-i, --ids ", "IDs for the interactive map layer") 292 | .option("--token ", "a external token to clear another user's layer data") 293 | .option("--force", "skip the confirmation prompt") 294 | .action(async (catalogHrn, layerId, options) => { 295 | try { 296 | await catalogUtil.validateCatalogAndLayer(catalogHrn, layerId);//validate catalogHrn and layerId 297 | xyzutil.setCatalogHrn(catalogHrn); 298 | await xyzutil.clearSpace(layerId, options); 299 | } catch(error) { 300 | common.handleError(error); 301 | } 302 | }); 303 | 304 | program 305 | .command("token") 306 | .description("Get workspace token") 307 | //.option("--console","opens web console for Data Hub") 308 | .action(async (options) => { 309 | try { 310 | console.log(await common.getWorkspaceToken()); 311 | } catch(error) { 312 | common.handleError(error); 313 | }; 314 | }); 315 | 316 | common.validate( 317 | [ 318 | "upload", 319 | "show", 320 | "delete", 321 | "clear", 322 | "token", 323 | "create", 324 | "config", 325 | "list", 326 | "ls" 327 | ], 328 | [process.argv[2]], 329 | program 330 | ); 331 | program.name('here iml').parse(process.argv); -------------------------------------------------------------------------------- /src/here-studio.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | import * as common from './common'; 28 | import {handleError, execute} from "./common"; 29 | import * as program from 'commander'; 30 | import * as inquirer from "inquirer"; 31 | const commands = ["list", "open", "show", "delete"]; 32 | 33 | const studioBaseURL = "https://studio.here.com"; 34 | const projectsUrl = "/project-api/projects"; 35 | 36 | program 37 | .name('here studio') 38 | .version('0.1.0'); 39 | 40 | program 41 | .command("list") 42 | .description("information about available xyz studio projects") 43 | .action(async function (options) { 44 | listProjects(options) 45 | .catch((error: any) => { 46 | handleError(error); 47 | }) 48 | }); 49 | 50 | program 51 | .command("delete ") 52 | .description("delete the project with the given id") 53 | .option("--force", "skip the confirmation prompt") 54 | .action(async (projectId, options) => { 55 | deleteProject(projectId, options) 56 | .catch((error) => { 57 | handleError(error, false); 58 | }) 59 | }); 60 | 61 | program 62 | .command("show ") 63 | .description("open the project with the given id") 64 | .action(async (projectId, options) => { 65 | showProject (projectId) 66 | .catch((error) => { 67 | handleError(error); 68 | }) 69 | }); 70 | 71 | async function showProject (id : any) { 72 | const response = await getProject(id,{}); 73 | if(response && response.body){ 74 | const projectData = JSON.parse(response.body); 75 | if(projectData.status.toUpperCase() === "PUBLISHED"){ 76 | const open = require("open"); 77 | open( 78 | studioBaseURL+"/viewer/?project_id="+id 79 | , { wait: false }); 80 | } else { 81 | console.log("FAILED: Project is not published."); 82 | console.log("You can publish this project at: https://studio.here.com."); 83 | } 84 | } else { 85 | console.log("FAILED: Project does not exist or project is not published."); 86 | } 87 | } 88 | 89 | async function deleteProject (id : any, options: any) { 90 | 91 | if (!options.force) { 92 | console.log("Are you sure you want to delete this project?") 93 | let answer: any = await inquirer.prompt([ 94 | { 95 | type: 'input', 96 | name: 'confirmed', 97 | message: 'Enter (Y)es to continue or (N)o to cancel' 98 | } 99 | ]); 100 | if (answer.confirmed 101 | && answer.confirmed.toUpperCase() !== 'Y' 102 | && answer.confirmed.toUpperCase() !== 'YES') { 103 | process.exit(1); 104 | } 105 | } 106 | console.log("Deleting project : "+id) 107 | 108 | //If project exists send a DELETE request for that projectID 109 | const uri = projectsUrl+"/"+id; 110 | const cType = ""; 111 | let response = await execute (uri, "DELETE", cType, "", options.token); 112 | 113 | if (response && response.statusCode === 204) { 114 | console.log("Successfully deleted project.") 115 | } 116 | else { 117 | console.log("Unable to delete project having project-id: "+id) 118 | } 119 | } 120 | 121 | 122 | /** 123 | * Will fetch all projects 124 | * @param options 125 | */ 126 | async function getAllProjects (options:any) { 127 | try { 128 | const uri = "/project-api/projects"; 129 | const cType = ""; 130 | let response = await execute(uri, "GET", cType, "", options.token); 131 | return response; 132 | } catch (error) { 133 | console.log("Unable to get all project data") 134 | return null; 135 | } 136 | } 137 | 138 | /** 139 | * Will get the project based on given id 140 | * @param id - Input project id 141 | * @param options 142 | */ 143 | async function getProject (id:string,options:any) { 144 | try { 145 | let uri = "/project-api/projects/"+id; 146 | let cType = ""; 147 | let response = await execute(uri, "GET", cType, "", options.token, false, false); 148 | return response 149 | } catch (error) { 150 | console.log("Unable to get project data") 151 | return null; 152 | } 153 | } 154 | 155 | 156 | /** 157 | * Will list all the projects for the given user in below format 158 | * 159 | * @param options 160 | */ 161 | export async function listProjects (options: any) { 162 | console.log("Please wait; Fetching your list of projects...") 163 | 164 | let response = await getAllProjects(options) 165 | let body = JSON.parse(response.body); 166 | if (response.body.length == 0) { 167 | console.log("No xyz projects found"); 168 | } else { 169 | let fields = ["id", "title", "status"]; 170 | 171 | //Flattened array of project JsonObjects containing info about name, id and description, add any other info later as necessary 172 | let extractProjectInfo: any[] = new Array(); 173 | 174 | //Iterate through all the projects and extract meta information in extractColumns Array having JSON Objects with keys of id, name and description, 175 | body.map((currentProject:any) => { 176 | 177 | //Check whether meta info like project description and name exists for that project? - > If exists Push the meta info with id in new 178 | if (currentProject.hasOwnProperty("meta")) { 179 | let viewerURL = ""; 180 | if (currentProject.status.toUpperCase() === "PUBLISHED") { 181 | viewerURL = "https://xyz.here.com/viewer/?project_id=" + currentProject.id; 182 | } 183 | let currentProjectDetails = { 184 | id: currentProject.id, 185 | title: currentProject.meta.name, 186 | description: currentProject.meta.description, 187 | status: currentProject.status, 188 | viewerURL 189 | } 190 | extractProjectInfo.push(currentProjectDetails) 191 | } 192 | }) 193 | 194 | //List the project 195 | common.drawNewTable(extractProjectInfo, fields, [40, 25, 12]); 196 | } 197 | } 198 | 199 | common.validate(commands, [process.argv[2]], program); 200 | program.parse(process.argv); 201 | if (!program.args.length) { 202 | common.verify(); 203 | } 204 | -------------------------------------------------------------------------------- /src/here-transform.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | import * as program from 'commander'; 28 | import * as common from './common'; 29 | import * as transform from './transformutil'; 30 | 31 | const prompter = require('prompt'); 32 | 33 | const commands = ["csv2geo", "shp2geo", "gpx2geo"]; 34 | 35 | program 36 | .version('0.1.0'); 37 | 38 | program 39 | .command('csv2geo ') 40 | .description('convert csv to geojson') 41 | .option('-y, --lat [lat]', 'latitude field name') 42 | .option('-x, --lon [lon]', 'longitude field name') 43 | // .option('-z, --alt [alt]', 'altitude field name') // not used in geojson 44 | .option('-d, --delimiter [,]', 'delimiter used in csv', ',') 45 | .option('-q, --quote ["]', 'quote used in csv', '"') 46 | .option('-z, --point [point]', 'points field name') 47 | .option('--string-fields ', 'comma seperated property names which needs to be converted as String even though they are numbers or boolean e.g. postal code') 48 | .action(async function (path, opt) { 49 | transform.read(path, true, { headers: true, delimiter: opt.delimiter, quote: opt.quote }).then(async result => { 50 | const json = JSON.stringify({ features: await transform.transform(result, opt), type: "FeatureCollection" }, null, 3); //Converted json object from csv data 51 | console.log(json); 52 | }); 53 | }); 54 | 55 | program 56 | .command('shp2geo ') 57 | .description('convert shapefile to geojson') 58 | .action(function (path, opt) { 59 | transform.readShapeFile(path).then(fc => { 60 | console.log(JSON.stringify(fc)); 61 | }); 62 | }); 63 | 64 | program 65 | .command('gpx2geo ') 66 | .description('convert gpx to geojson') 67 | .action(async function (path, opt) { 68 | transform.read(path, false, { }).then(async result => { 69 | //console.log(result) 70 | const json = JSON.stringify({ features: await transform.transformGpx(result, opt), type: "FeatureCollection" }, null, 3); //Converted json object from gpx data 71 | console.log(json); 72 | }); 73 | }); 74 | 75 | common.validate(commands, [process.argv[2]], program); 76 | prompter.stop(); 77 | program.name('here transform').parse(process.argv); 78 | if (!program.args.length) { 79 | common.verify(); 80 | } 81 | -------------------------------------------------------------------------------- /src/here.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | import * as common from "./common"; 28 | import * as inquirer from 'inquirer'; 29 | 30 | const program = require('commander'); 31 | const settings = require('user-settings').file('.herecli'); 32 | const latestVersion = require('latest-version'); 33 | 34 | const commands = ["xyz", "studio", "xs","c","configure", "transform","tf", "help", "geocode","gc", "iml", "interactivemap", "catalog"]; 35 | const fs = require('fs'); 36 | const path = require('path'); 37 | 38 | const questionLicense = [ 39 | { 40 | type: 'input', 41 | name: 'license', 42 | message: 'Enter (A)ccept or (D)ecline to proceed' 43 | } 44 | ]; 45 | 46 | async function start() { 47 | process.removeAllListeners('warning'); 48 | process.env.NODE_NO_WARNINGS = '1'; 49 | if (settings.get('GAlicense') === 'true') { 50 | await checkVersion(); 51 | } else { 52 | await showLicenseConfirmation(); 53 | } 54 | 55 | program 56 | .version(getVersion()) 57 | .command('configure [verify|refresh]', 'setup configuration for authentication').alias('c') 58 | .command('studio [list|delete|show]', 'work with HERE Studio projects') 59 | .command('transform [csv2geo|shp2geo|gpx2geo]', 'convert from csv/shapefile/gpx to geojson').alias('tf') 60 | .command('geocode', 'geocode feature').alias('gc'); 61 | if(settings.get('workspaceMode') === 'true'){ 62 | program.command('interactivemap [list|create|upload]', 'work with Interactive map layers').alias('iml') 63 | program.command('catalog [list|create]', 'work with workspace catalog') 64 | } else { 65 | program.command('xyz [list|create|upload]', 'work with Data Hub spaces').alias('xs') 66 | } 67 | program.parse(process.argv); 68 | common.validate(commands, program.args, program); 69 | } 70 | 71 | start().catch(err => console.log(err)); 72 | 73 | function getVersion() { 74 | const pkg = require('../package.json'); 75 | return pkg.version; 76 | } 77 | 78 | async function checkVersion() { 79 | const version = getVersion(); 80 | const hrTime = process.hrtime(); 81 | const ctime = hrTime[0] * 1000 + hrTime[1] / 1000000; 82 | const ltime = settings.get('lastAccessTime'); 83 | const lastAccessVersion = getLastAccessVersion(ctime, ltime); 84 | if (lastAccessVersion && (version == lastAccessVersion)) { 85 | //version matched with cached version 86 | return; 87 | } 88 | 89 | const pv = await latestVersion('@here/cli'); 90 | if (pv > version) { 91 | console.log("herecli('" + version + "') is out of date. Latest version is " + pv + ". Use command 'npm install -g @here/cli' to update to the latest version"); 92 | process.exit(1); 93 | } 94 | // version matched with current version. We are up to date 95 | settings.set('lastAccessVersion', pv); 96 | settings.set('lastAccessTime', ctime); 97 | } 98 | 99 | async function showLicenseConfirmation() { 100 | console.log(fs.readFileSync(path.resolve(__dirname, 'beta-terms.txt'), 'utf8')); 101 | try { 102 | const open = require("open"); 103 | open("http://explore.xyz.here.com/terms-and-conditions",{wait:false}); 104 | } catch { 105 | } 106 | 107 | const answer = await inquirer.prompt<{ license?: string }>(questionLicense); 108 | 109 | const termsResp = answer.license ? answer.license.toLowerCase() : 'decline'; 110 | if (termsResp === "a" || termsResp === "accept") { 111 | settings.set('GAlicense', 'true'); 112 | await checkVersion(); 113 | } else { 114 | console.log("In order to use the HERE CLI, you will need to (A)ccept the license agreement. If you would like to remove the HERE CLI installed by npm, please enter npm uninstall -g @here/cli"); 115 | process.exit(1); 116 | } 117 | } 118 | 119 | function getLastAccessVersion(ctime: number, ltime: number | undefined) { 120 | const time = (ctime - (ltime ? ltime : 0)) / (1000 * 60); 121 | const lastAccessVersion = settings.get('lastAccessVersion'); 122 | if (time > 15) { 123 | settings.set('lastAccessVersion', null); 124 | settings.set('lastAccessTime', null); 125 | return null; 126 | } 127 | return lastAccessVersion; 128 | } 129 | -------------------------------------------------------------------------------- /src/hexbin.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | import * as turf from '@turf/helpers'; 28 | import * as common from "./common"; 29 | import * as h3 from "h3-js"; 30 | const geojson2h3 = require('geojson2h3'); 31 | 32 | const cosines: number[] = []; 33 | const sines: number[] = []; 34 | for (let i = 0; i < 6; i++) { 35 | const angle = 2 * Math.PI / 6 * i; 36 | cosines.push(Math.cos(angle)); 37 | sines.push(Math.sin(angle)); 38 | } 39 | const hexagonAngle = 0.523598776; //30 degrees in radians 40 | 41 | export function getHexBin(point: number[], cellSize: number, isMeters: boolean){ 42 | let degreesCellSize; 43 | if(isMeters){ 44 | degreesCellSize = (cellSize/1000)/(111.111 * Math.cos(point[1] * Math.PI / 180)); 45 | } else { 46 | degreesCellSize = cellSize; 47 | } 48 | const finalHexRootPoint = getSelectedHexagon(point[1],point[0],degreesCellSize); 49 | let data= hexagon(finalHexRootPoint,degreesCellSize,degreesCellSize,null,cosines,sines); 50 | return data; 51 | } 52 | 53 | export function getH3HexbinArea(resolution: number){ 54 | let area = h3.hexArea(resolution, "km2") 55 | return area 56 | } 57 | 58 | export function getH3HexbinChildren(h3Index: string, resolution: number){ 59 | let children = h3.h3ToChildren(h3Index,resolution) 60 | console.log({children}) 61 | return children 62 | } 63 | 64 | function getH3HexBin(point: number[], cellSize: number){ 65 | let h3Index = h3.geoToH3(point[1],point[0], cellSize); 66 | const hexCenter = h3.h3ToGeo(h3Index); 67 | let hexFeature = geojson2h3.h3ToFeature(h3Index); 68 | hexFeature.properties.centroid = [hexCenter[1], hexCenter[0]]; 69 | hexFeature.id = h3Index; 70 | return hexFeature; 71 | } 72 | 73 | //here x and y is inverse, x is latitude and y is longitude 74 | function getSelectedHexagon(x: number, y: number, degreesCellSize: number){ 75 | let xinverse, yinverse = false; 76 | if(x < 0){ 77 | xinverse = true; 78 | x = -x; 79 | } 80 | if(y < 0){ 81 | yinverse = true; 82 | y = -y; 83 | } 84 | let hexRootPoint = getMoldulusHexagon(x,y,degreesCellSize); 85 | if(xinverse){ 86 | hexRootPoint[1] = -hexRootPoint[1]; 87 | } 88 | if(yinverse){ 89 | hexRootPoint[0] = -hexRootPoint[0]; 90 | } 91 | return hexRootPoint; 92 | } 93 | 94 | //here x and y is inverse, x is latitude and y is longitude 95 | function getMoldulusHexagon(x:number, y:number, degreesCellSize:number) 96 | { 97 | //y = y - (degreesCellSize / 2); //decrease hlaf cellsize because our grid is not starting from 0,0 which is having half hexagon 98 | const c = Math.sin(hexagonAngle) * degreesCellSize; //height between side length and hex top point 99 | const gridHeight = degreesCellSize + c; 100 | const halfWidth = Math.cos(hexagonAngle) * degreesCellSize; 101 | const gridWidth = halfWidth * 2; 102 | 103 | // Find the row and column of the box that the point falls in. 104 | let row; 105 | let column; 106 | 107 | if (y < (degreesCellSize / 2)){ 108 | row = -1; 109 | if(x < halfWidth) { 110 | column = 0; 111 | } else { 112 | column = Math.ceil( (x - halfWidth) / gridWidth); 113 | } 114 | } else { 115 | y = y - (degreesCellSize / 2); 116 | row = Math.floor(y / gridHeight); 117 | const rowIsOdd = row % 2 == 1; 118 | 119 | // Is the row an odd number? 120 | if (rowIsOdd)// Yes: Offset x to match the indent of the row 121 | column = Math.floor((x - halfWidth) / gridWidth); 122 | else// No: Calculate normally 123 | column = Math.floor(x / gridWidth); 124 | 125 | // Work out the position of the point relative to the box it is in 126 | const relY = y - (row * gridHeight) //- (degreesCellSize / 2);//decrease half cellsize because our grid is not starting from 0,0 which is having half hexagon 127 | let relX; 128 | 129 | if (rowIsOdd) { 130 | relX = (x - (column * gridWidth)) - halfWidth; 131 | } else { 132 | relX = x - (column * gridWidth); 133 | } 134 | 135 | const m = c / halfWidth; 136 | if (relY < (-m * relX) + c) // LEFT edge 137 | { 138 | row--; 139 | if (!rowIsOdd && row > 0){ 140 | column--; 141 | } 142 | } else if (relY < (m * relX) - c) // RIGHT edge 143 | { 144 | row--; 145 | if (rowIsOdd || row < 0){ 146 | column++; 147 | } 148 | } 149 | } 150 | //console.log("hexagon row " + row + " , column " + column); 151 | 152 | const lat = (column * gridWidth + ((row % 2) * halfWidth)) + halfWidth; 153 | const lon = (row * (c + degreesCellSize)) + c + (degreesCellSize); 154 | return [round(lon,6),round(lat,6)]; 155 | } 156 | 157 | function round(value:number, decimals:number) { 158 | if(!("" + value).includes("e")) { 159 | return Number(Math.round(Number(value + 'e' + decimals)) + 'e-' + decimals); 160 | } else { 161 | var arr = ("" + value).split("e"); 162 | var sig = ""; 163 | if(+arr[1] + decimals > 0) { 164 | sig = "+"; 165 | } 166 | return Number(+(Math.round(Number(+arr[0] + "e" + sig + (+arr[1] + decimals))) + "e-" + decimals)); 167 | } 168 | } 169 | 170 | /** 171 | * Creates hexagon 172 | * 173 | * @private 174 | * @param {Array} center of the hexagon 175 | * @param {number} rx half hexagon width 176 | * @param {number} ry half hexagon height 177 | * @param {Object} properties passed to each hexagon 178 | * @param {Array} cosines precomputed 179 | * @param {Array} sines precomputed 180 | * @returns {Feature} hexagon 181 | */ 182 | function hexagon(center:number[], rx:number, ry:number, properties:any, cosines:number[], sines:number[]): any { 183 | const vertices = []; 184 | for (let i = 0; i < 6; i++) { 185 | const x = round(center[0] + rx * cosines[i],6); 186 | const y = round(center[1] + ry * sines[i],6); 187 | vertices.push([x, y]); 188 | } 189 | //first and last vertex must be the same 190 | vertices.push(vertices[0].slice()); 191 | let feature = turf.polygon([vertices], properties); 192 | feature.properties.centroid = center; 193 | return feature; 194 | } 195 | 196 | export function calculateHexGrids(features:any[], cellSize:number, isAddIds:boolean, groupByProperty:string, aggregate:string, cellSizeLatitude: number, useH3Library: boolean, existingHexFeatures:any[]){ 197 | let gridMap: any={}; 198 | let maxCount = 0; 199 | let maxSum = 0; 200 | let groupPropertyCount: any = {}; 201 | if(existingHexFeatures && Array.isArray(existingHexFeatures)){ 202 | existingHexFeatures.forEach(function (hexFeature){ 203 | gridMap[hexFeature.id] = hexFeature; 204 | if(hexFeature.properties.count > maxCount){ 205 | maxCount = hexFeature.properties.count; 206 | } 207 | if(hexFeature.properties.sum && hexFeature.properties.sum.sum > maxSum){ 208 | maxSum = hexFeature.properties.sum.sum; 209 | } 210 | if(hexFeature.properties.subcount != null){ 211 | for (const key of Object.keys(hexFeature.properties.subcount)) { 212 | if(groupPropertyCount[key] == null){ 213 | groupPropertyCount[key] = {}; 214 | groupPropertyCount[key].maxCount = hexFeature.properties.subcount[key].maxCount; 215 | } 216 | } 217 | } 218 | }); 219 | } 220 | //let minCount = Number.MAX_SAFE_INTEGER; 221 | const degreesCellSize = (cellSize/1000)/(111.111 * Math.cos(cellSizeLatitude * Math.PI / 180)); 222 | features.forEach(function (feature, i){ 223 | if (feature.geometry != null && feature.geometry.type != null && (feature.geometry.type.toLowerCase() === 'point' || feature.geometry.type.toLowerCase() === 'linestring' || feature.geometry.type.toLowerCase() === 'multilinestring')) { 224 | if(!(feature.properties != null && feature.properties['@ns:com:here:xyz'] != null 225 | && feature.properties['@ns:com:here:xyz'].tags != null && feature.properties['@ns:com:here:xyz'].tags.includes('centroid'))){ 226 | let x; 227 | let point = []; 228 | if(feature.geometry.type.toLowerCase() === 'point'){ 229 | point = feature.geometry.coordinates; 230 | } else { 231 | let points: number[][] = []; 232 | if(feature.geometry.type.toLowerCase() === 'linestring'){ 233 | points = feature.geometry.coordinates; 234 | } else if(feature.geometry.type.toLowerCase() === 'multilinestring'){ 235 | for(const line of feature.geometry.coordinates){ 236 | points = points.concat(line); 237 | } 238 | } 239 | if(points.length > 2){ 240 | point = points[Math.round(points.length/2)]; 241 | } else { 242 | point[0] = (points[0][0] + points[1][0]) / 2; 243 | point[1] = (points[0][1] + points[1][1]) / 2; 244 | } 245 | } 246 | if(useH3Library){ 247 | x = getH3HexBin(point, cellSize); 248 | } else { 249 | x = getHexBin(point, degreesCellSize, false); 250 | } 251 | if (x) { 252 | let gridId; 253 | if(useH3Library){ 254 | gridId = x.id; 255 | } else { 256 | gridId = common.md5Sum(JSON.stringify(x.geometry)); 257 | x.id = gridId; 258 | } 259 | if (!x.properties) { 260 | x.properties = {}; 261 | x.properties['count'] = 0; 262 | } 263 | let outGrid = x; 264 | if (gridMap[gridId]) { 265 | outGrid = gridMap[gridId]; 266 | } else { 267 | if (isAddIds) { 268 | outGrid.properties.ids = new Array(); 269 | } 270 | gridMap[gridId] = outGrid; 271 | outGrid.properties.count = 0; 272 | } 273 | outGrid.properties.count = outGrid.properties.count + 1; 274 | if(outGrid.properties.count > maxCount){ 275 | maxCount = outGrid.properties.count; 276 | } 277 | /* 278 | if(outGrid.properties.count < minCount){ 279 | minCount = outGrid.properties.count; 280 | }*/ 281 | if (isAddIds) { 282 | outGrid.properties.ids.push(feature.id); 283 | } 284 | 285 | if(aggregate){ 286 | if(isNaN(feature.properties[aggregate])){ 287 | throw new Error("Property " + aggregate + " is not numeric for feature - " + JSON.stringify(feature)); 288 | } 289 | if(!outGrid.properties.sum){ 290 | outGrid.properties.sum = {}; 291 | outGrid.properties.sum.sum = 0; 292 | outGrid.properties.sum.property_name = aggregate; 293 | } 294 | outGrid.properties.sum.sum += Number(feature.properties[aggregate]); 295 | if(outGrid.properties.sum.sum > maxSum){ 296 | maxSum = outGrid.properties.sum.sum; 297 | } 298 | } 299 | 300 | //GroupBy property logic 301 | //console.log(groupByProperty); 302 | if(groupByProperty){ 303 | let propertyValue = feature.properties[groupByProperty]; 304 | //console.log(propertyValue); 305 | if (groupPropertyCount[propertyValue] == null || groupPropertyCount[propertyValue].maxCount == null) { 306 | groupPropertyCount[propertyValue] = {}; 307 | groupPropertyCount[propertyValue].maxCount = 0; 308 | } 309 | if(outGrid.properties.subcount == null) { 310 | outGrid.properties.subcount = {}; 311 | outGrid.properties.subcount.property_name = groupByProperty; 312 | } 313 | if(outGrid.properties.subcount[propertyValue] == null){ 314 | outGrid.properties.subcount[propertyValue] = {}; 315 | outGrid.properties.subcount[propertyValue].count = 0; 316 | } 317 | outGrid.properties.subcount[propertyValue].count++; 318 | if(outGrid.properties.subcount[propertyValue].count > groupPropertyCount[propertyValue].maxCount){ 319 | groupPropertyCount[propertyValue].maxCount = outGrid.properties.subcount[propertyValue].count; 320 | } 321 | } 322 | gridMap[gridId] = outGrid; 323 | } else { 324 | console.error("something went wrong and hexgrid is not available for feature - " + feature); 325 | throw new Error("something went wrong and hexgrid is not available for feature - " + feature); 326 | } 327 | } 328 | } 329 | }); 330 | let hexFeatures=new Array(); 331 | for(const k in gridMap){ 332 | let feature = gridMap[k]; 333 | //feature.properties.minCount = minCount; 334 | feature.properties.maxCount = maxCount; 335 | feature.properties.occupancy = feature.properties.count/maxCount; 336 | feature.properties.color = "hsla(" + (200 - Math.round(feature.properties.occupancy*100*2)) + ", 100%, 50%,0.51)"; 337 | if(aggregate){ 338 | feature.properties.sum.maxSum = maxSum; 339 | feature.properties.sum.average = feature.properties.sum.sum / feature.properties.count; 340 | } 341 | hexFeatures.push(feature); 342 | if(groupByProperty){ 343 | for (const key of Object.keys(feature.properties.subcount)) { 344 | if(key != 'property_name'){ 345 | feature.properties.subcount[key].maxCount = groupPropertyCount[key].maxCount; 346 | feature.properties.subcount[key].occupancy = feature.properties.subcount[key].count/groupPropertyCount[key].maxCount; 347 | feature.properties.subcount[key].color = "hsla(" + (200 - Math.round(feature.properties.subcount[key].occupancy*100*2)) + ", 100%, 50%,0.51)"; 348 | //console.log(key, JSON.stringify(feature.properties.subcount[key])); 349 | } 350 | } 351 | } 352 | } 353 | return hexFeatures; 354 | } 355 | 356 | /** 357 | //let point = [13.4015825,52.473507]; 358 | let point = [ 359 | //13.4015825,52.473507 360 | //0.4015825,0.473507 361 | //13.401877284049988, 362 | // 52.473625332625154 363 | //13.401110172271729, 364 | // 52.47341620511857 365 | //13.401729762554169, 366 | // 52.47346521946711 367 | 0.003519058227539062, 368 | 0.0005149841308648958 369 | ]; 370 | let feature = {'geometry':{'coordinates':point,'type':'Point'},'properties':{},'type':'Feature'}; 371 | //console.log(feature); 372 | let result = getHexBin(feature,100); 373 | //console.log(JSON.stringify(result)); 374 | let features = []; 375 | features.push(feature); 376 | features.push(result); 377 | let featureCollection = {'type':'FeatureCollection','features':features}; 378 | console.log(JSON.stringify(featureCollection, null, 2)); 379 | */ 380 | -------------------------------------------------------------------------------- /src/requestAsync.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 - 2021 HERE Europe B.V. 3 | SPDX-License-Identifier: MIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | const got = require('got'); 26 | import { ApiError } from "./api-error"; 27 | // async wrapper around request 28 | export function requestAsync(options: any): Promise 29 | { 30 | return new Promise(async (resolve, reject) => { 31 | try{ 32 | let result = await got(options); 33 | resolve(result); 34 | } catch(e){ 35 | if(e.response && e.response.body && e.response.statusCode){ 36 | resolve({statusCode:e.response.statusCode, body:JSON.stringify(e.response.body)}); 37 | } else if(e.response){ 38 | reject(e.response); 39 | } else { 40 | reject(e); 41 | } 42 | } 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /src/sso.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | import { requestAsync } from "./requestAsync"; 28 | 29 | const url = "https://account.here.com/sign-in?client-id=es1HEn2LGqFocvfD1eEt&version=3&sdk=true&type=frame&uri=https%3A%2F%2Fxyz.here.com&sign-in-screen-config=password,heread&track-id=trackUPMUI&lang=en-us"; 30 | const signInURL ="https://account.here.com/api/account/sign-in-with-password"; 31 | const xyzRoot = "https://xyz.api.here.com"; 32 | 33 | const tokenURL = xyzRoot+"/token-api/tokens?tokenType=PERMANENT" 34 | 35 | export async function executeWithCookie(userName: string, password: string) { 36 | 37 | const response = await requestAsync({ url }); 38 | 39 | let csrfToken = response.body as string; 40 | 41 | const cookies = response.headers['set-cookie']; 42 | csrfToken = csrfToken.substring(csrfToken.indexOf("csrf")); 43 | csrfToken = csrfToken.substring(csrfToken.indexOf(':') + 3, csrfToken.indexOf(',') - 1); 44 | const requestBody = `{"realm":"here","email":"${userName}","password":"${password}","rememberMe":true}`; 45 | const options = { 46 | url: signInURL, 47 | method: 'POST', 48 | headers: { 49 | "Cookie": extractCookies(cookies, ["here_account","here_account.sig"]), 50 | "x-csrf-token": csrfToken, 51 | "Content-Type": "application/json" 52 | }, 53 | body : requestBody 54 | }; 55 | 56 | const res = await requestAsync(options); 57 | 58 | if (res.statusCode !== 200){ 59 | throw new Error("Error while Authenticating. Please check credentials and try again."); 60 | } 61 | 62 | const mainCookie = extractCookies(res.headers['set-cookie'], ["here"]); 63 | return mainCookie; 64 | } 65 | 66 | function extractCookies(cookies: string[] | undefined, expectedKeys: string[]) { 67 | let returnCookie = ""; 68 | let app = ""; 69 | 70 | if (cookies === undefined) 71 | return returnCookie; 72 | 73 | cookies.forEach(cookie => { 74 | expectedKeys.forEach(key => { 75 | if (cookie.startsWith(key)){ 76 | returnCookie += app + cookie.split(";")[0]; 77 | app = ";"; 78 | } 79 | }); 80 | }); 81 | return returnCookie; 82 | } 83 | 84 | export async function fetchToken(cookies: string, requestBody: any, appId : string, expirationTime: number = 0) { 85 | let body : any = { "urm": requestBody, cid: appId }; 86 | if(expirationTime){ 87 | body['exp'] = expirationTime; 88 | } 89 | const options = { 90 | url: tokenURL, 91 | method: "POST", 92 | body: JSON.stringify(body), 93 | headers: { 94 | "Cookie":cookies, 95 | "Content-Type":"application/json" 96 | } 97 | } 98 | const response = await requestAsync(options); 99 | if (response.statusCode < 200 || response.statusCode >= 300) 100 | throw new Error("Error while fetching token: " + response.body); 101 | 102 | return JSON.parse(response.body); 103 | } 104 | -------------------------------------------------------------------------------- /src/summary.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | import * as common from "./common"; 28 | 29 | export function summarize(features: any[], spaceId: string, upload: boolean, options: any) { 30 | let set1 = new Set(); 31 | const tagCountMap: { [fieldName: string]: any } = {}; 32 | const gemetryMap: { [fieldName: string]: any } = {}; 33 | const dateRanges = { minUpdated: Infinity, maxUpdated: 0, minCreated: Infinity, maxCreated: 0 }; 34 | let globalTagsArr: Array; 35 | if(options.tags){ 36 | globalTagsArr = options.tags.toLowerCase().split(','); 37 | } 38 | 39 | features.forEach(element => { 40 | let tags: string[] = []; 41 | if(element.properties["@ns:com:here:xyz"] && element.properties["@ns:com:here:xyz"].tags){ 42 | tags = element.properties["@ns:com:here:xyz"].tags as string[]; 43 | } 44 | if(globalTagsArr){ 45 | tags = tags.concat(globalTagsArr); 46 | } 47 | const geoType = (element.geometry) ? element.geometry["type"] : null; 48 | 49 | const updatedAt = element.properties["@ns:com:here:xyz"].updatedAt; 50 | if (updatedAt < dateRanges.minUpdated) { 51 | dateRanges.minUpdated = updatedAt; 52 | } 53 | 54 | if (updatedAt > dateRanges.maxUpdated) { 55 | dateRanges.maxUpdated = updatedAt; 56 | } 57 | const createdAt = element.properties["@ns:com:here:xyz"].createdAt; 58 | if (createdAt < dateRanges.minCreated) { 59 | dateRanges.minCreated = createdAt; 60 | } 61 | 62 | if (createdAt > dateRanges.maxCreated) { 63 | dateRanges.maxCreated = createdAt; 64 | } 65 | if (geoType) { 66 | if (gemetryMap[geoType]) { 67 | gemetryMap[geoType] = gemetryMap[geoType] + 1; 68 | } else { 69 | gemetryMap[geoType] = 1; 70 | } 71 | } 72 | 73 | tags.forEach(tag => { 74 | set1.add(tag); 75 | if (tagCountMap[tag]) { 76 | tagCountMap[tag] = tagCountMap[tag] + 1; 77 | } else { 78 | tagCountMap[tag] = 1; 79 | } 80 | }); 81 | }); 82 | const myArr = Array.from(set1); 83 | const summaryObject = { "count": features.length, "tagInfo": { "uniqueTagCount": myArr.length, "allTags": myArr, tagSummary: tagCountMap, gemetryMap: gemetryMap, dateRanges: dateRanges}, catalogHrn: options.catalogHrn }; 84 | generateSummaryText(spaceId, summaryObject, upload); 85 | } 86 | 87 | function generateSummaryText(spaceId: string, summaryObject: any, upload: boolean) { 88 | console.log("=========================================================="); 89 | if (upload) { 90 | console.log(" Upload Summary "); 91 | } else { 92 | console.log(" Summary for Space " + spaceId); 93 | } 94 | console.log("=========================================================="); 95 | console.log("Total " + summaryObject.count + " features"); 96 | printGeometry(summaryObject); 97 | 98 | if(!summaryObject.catalogHrn) { 99 | console.log("Total unique tag Count : " + summaryObject.tagInfo.uniqueTagCount); 100 | console.log("Unique tag list :" + JSON.stringify(summaryObject.tagInfo.allTags)); 101 | printTags(summaryObject); 102 | } 103 | if (!upload) { 104 | printDateRanges(summaryObject); 105 | } 106 | } 107 | 108 | function printGeometry(summaryObject: any) { 109 | const geometryR = new Array(); 110 | for (const x in summaryObject.tagInfo.gemetryMap) { 111 | geometryR.push({ GeometryType: x, Count: summaryObject.tagInfo.gemetryMap[x] }); 112 | } 113 | if (geometryR.length > 0) { 114 | common.drawTable(geometryR, ["GeometryType", "Count"]); 115 | } else { 116 | console.log("No geometry object found"); 117 | } 118 | } 119 | 120 | function printTags(summaryObject: any) { 121 | const tags = new Array(); 122 | for (const x in summaryObject.tagInfo.tagSummary) { 123 | tags.push({ TagName: x, Count: summaryObject.tagInfo.tagSummary[x] }); 124 | } 125 | tags.sort(function (a, b) { 126 | return b.Count - a.Count; 127 | }); 128 | common.drawTable(tags, ["TagName", "Count"]); 129 | } 130 | 131 | function printDateRanges(summaryObject: any) { 132 | console.log("Features created from " + common.timeStampToLocaleString(summaryObject.tagInfo.dateRanges.minCreated) + " to " + common.timeStampToLocaleString(summaryObject.tagInfo.dateRanges.maxCreated)); 133 | console.log("Features updated from " + common.timeStampToLocaleString(summaryObject.tagInfo.dateRanges.minUpdated) + " to " + common.timeStampToLocaleString(summaryObject.tagInfo.dateRanges.maxUpdated)); 134 | } 135 | 136 | 137 | export function analyze(features: any[], properties: string[], spaceId: string) { 138 | const propSummary: { [fieldName: string]: any } = {}; 139 | //console.log(features); 140 | features.forEach(element => { 141 | element.properties.id = element.id; 142 | element = element.properties; 143 | properties.forEach(prop => { 144 | let tag = prop + ":" + element[prop]; 145 | if (propSummary[tag]) { 146 | let cObj = propSummary[tag]; 147 | cObj.Count = cObj.Count + 1; 148 | } else { 149 | propSummary[tag] = { PropertyName: prop, Value: element[prop], Count: 1 }; 150 | } 151 | }); 152 | }); 153 | printProperties(propSummary, spaceId); 154 | } 155 | 156 | function printProperties(propSummary: any, spaceId: string) { 157 | const tags = new Array(); 158 | const uniqueTagCount = []; 159 | for (const x in propSummary) { 160 | tags.push(propSummary[x]); 161 | } 162 | const uniqueProps = tags.map(obj => obj.PropertyName) 163 | .filter((value, index, self) => self.indexOf(value) === index) 164 | const gropedTags = groupBy(tags, "PropertyName"); 165 | uniqueProps.sort(function (a, b) { 166 | return alphabetical(a, b); 167 | }); 168 | let arrangedTags = new Array(); 169 | uniqueProps.forEach(prop => { 170 | const da = gropedTags[prop]; 171 | da.sort(function (a: any, b: any) { 172 | return b.Count - a.Count; 173 | }); 174 | arrangedTags = arrangedTags.concat(gropedTags[prop]); 175 | }); 176 | common.drawTable(arrangedTags, ["PropertyName", "Value", "Count"]); 177 | printUniquePropertyName(arrangedTags, spaceId); 178 | } 179 | 180 | function alphabetical(a: string, b: string) { 181 | const a1 = a.toLowerCase(); 182 | const b1 = b.toLowerCase(); 183 | if (a1 < b1) { 184 | return -1; 185 | } else if (a1 > b1) { 186 | return 1; 187 | } else { 188 | return 0; 189 | } 190 | } 191 | 192 | function printUniquePropertyName(tags: any[], spaceId: string) { 193 | let uniqueTagCount: number[] = []; 194 | tags.forEach(tag => { 195 | let val = uniqueTagCount[tag.PropertyName]; 196 | if (val) 197 | val = val + 1; 198 | else 199 | val = 1; 200 | uniqueTagCount[tag.PropertyName] = val; 201 | }); 202 | const uProps = new Array(); 203 | for (const k in uniqueTagCount) { 204 | uProps.push({ PropertyName: k, Count: uniqueTagCount[k] }); 205 | } 206 | uProps.sort(function (a, b) { 207 | return b.Count - a.Count; 208 | }); 209 | console.log(`Total unique property values in space ${spaceId} : \n`); 210 | common.drawTable(uProps, ["PropertyName", "Count"]); 211 | } 212 | 213 | function groupBy(array: any[], prop: string) { 214 | let outMap: any[] = []; 215 | array.forEach(a => { 216 | const pName = a[prop]; 217 | let value = outMap[pName]; 218 | if (!value) { 219 | value = new Array(); 220 | outMap[pName] = value; 221 | console.log(pName); 222 | } 223 | value.push(a); 224 | }) 225 | return outMap; 226 | } 227 | -------------------------------------------------------------------------------- /src/transformutil.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (C) 2018 - 2021 HERE Europe B.V. 5 | SPDX-License-Identifier: MIT 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | import * as shapefile from "shapefile"; 28 | import * as fs from "fs"; 29 | import * as tmp from "tmp"; 30 | const got = require('got'); 31 | const pathLib = require('path'); 32 | const XLSX = require('xlsx'); 33 | import * as extract from "extract-zip"; 34 | import * as readline from "readline"; 35 | import { requestAsync } from "./requestAsync"; 36 | import * as common from "./common"; 37 | import * as proj4 from "proj4"; 38 | import * as inquirer from "inquirer"; 39 | import * as csv from 'fast-csv'; 40 | import { DOMParser } from '@xmldom/xmldom'; 41 | import {getSpaceDataFromXyz} from "./xyzutil"; 42 | 43 | const latArray = ["y", "ycoord", "ycoordinate", "coordy", "coordinatey", "latitude", "lat"]; 44 | const lonArray = ["x", "xcoord", "xcoordinate", "coordx", "coordinatex", "longitude", "lon", "lng", "long", "longitud"]; 45 | const altArray = ["z", "zcoord", "zcoordinate", "coordz", "coordinatez", "altitude", "alt"]; 46 | const pointArray = ["p", "point", "points"]; 47 | const wgs84prjString = 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'; 48 | 49 | export type FeatureCollection = { 50 | "type": "FeatureCollection", 51 | "features": Array 52 | }; 53 | 54 | let joinValueToFeatureIdMap: Map = new Map(); 55 | 56 | export function readShapeFile(path: string) { 57 | if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) { 58 | return new Promise((resolve, reject) => 59 | tmp.file({ mode: 0o644, prefix: '', postfix: path.indexOf('.zip') !== -1 ? '.zip':'.shp' }, function _tempFileCreated(err, tempFilePath, fd) { 60 | if (err) 61 | reject(err); 62 | 63 | const dest = fs.createWriteStream(tempFilePath); 64 | dest.on('finish', function (err: any) { 65 | if (err) 66 | reject(err); 67 | else 68 | resolve(readShapeFileInternal(tempFilePath)); 69 | }); 70 | got.stream(path) 71 | .on('error', (err: any) => reject(err)) 72 | .pipe(dest); 73 | }) 74 | ); 75 | } else { 76 | return readShapeFileInternal(path); 77 | } 78 | } 79 | 80 | export function readExcelFile(path: string) { 81 | if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) { 82 | return new Promise((resolve, reject) => 83 | tmp.file({ mode: 0o644, prefix: '', postfix: path.indexOf('.xlsx') !== -1 ? '.xlsx':'.xls' }, function _tempFileCreated(err, tempFilePath, fd) { 84 | if (err) 85 | reject(err); 86 | 87 | const dest = fs.createWriteStream(tempFilePath); 88 | dest.on('finish', function (err: any) { 89 | if (err) 90 | reject(err); 91 | else 92 | resolve(readExcelFileInternal(tempFilePath)); 93 | }); 94 | got.stream(path) 95 | .on('error', (err: any) => reject(err)) 96 | .pipe(dest); 97 | }) 98 | ); 99 | } else { 100 | return readExcelFileInternal(path); 101 | } 102 | } 103 | 104 | function readExcelFileInternal(path: string): any{ 105 | const workbook = XLSX.readFile(path, {sheetStubs: true}); 106 | return workbook; 107 | } 108 | 109 | async function readShapeFileInternal(path: string): Promise { 110 | const tmpDir = tmp.dirSync({"unsafeCleanup": true});; 111 | try { 112 | if(path.lastIndexOf('.zip') !== -1){ 113 | await extract(path, {'dir':tmpDir.name}); 114 | const shpFiles = fs.readdirSync(tmpDir.name, { withFileTypes: true }) 115 | .filter(dirent => dirent.isFile() && dirent.name.slice(-4).indexOf(".shp") !== -1) 116 | .map(dirent => dirent.name); 117 | if(shpFiles.length > 1){ 118 | console.log("Error - more than one shapefiles detected in zip file"); 119 | process.exit(0); 120 | } else if(shpFiles.length == 0){ 121 | console.log("Error - No shapefile detected in zip file"); 122 | process.exit(0); 123 | } 124 | path = pathLib.join(tmpDir.name, shpFiles[0]); 125 | } 126 | const fc: FeatureCollection = { "type": "FeatureCollection", "features": [] }; 127 | let isPrjFilePresent : boolean = false; 128 | let prjFilePath = path.substring(0,path.lastIndexOf('.shp')) + ".prj"; 129 | let prjFile: any = ''; 130 | if (isPrjFilePresent = fs.existsSync(prjFilePath)) { 131 | //console.log(prjFilePath + " file exists, using this file for crs transformation"); 132 | prjFile = await readDataFromFile(prjFilePath, false); 133 | } 134 | const source = await shapefile.open(path, undefined, { encoding: "UTF-8" }); 135 | 136 | while (true) { 137 | const result = await source.read(); 138 | 139 | if (result.done){ 140 | return fc; 141 | } 142 | let feature = result.value; 143 | if(isPrjFilePresent && prjFile.toString().trim() != wgs84prjString.toString().trim()){ 144 | feature = convertFeatureToPrjCrs(prjFile, feature); 145 | } 146 | 147 | fc.features.push(feature); 148 | } 149 | } finally { 150 | tmpDir.removeCallback(); 151 | } 152 | } 153 | 154 | function convertFeatureToPrjCrs(prjFile: string, feature: any){ 155 | if(feature.geometry.type == "Point"){ 156 | feature.geometry.coordinates = proj4(prjFile, wgs84prjString, feature.geometry.coordinates); 157 | } else if(feature.geometry.type == "MultiPoint" || feature.geometry.type == "LineString"){ 158 | let newCoordinates: any[] = []; 159 | feature.geometry.coordinates.forEach(function (value: number[]) { 160 | newCoordinates.push(proj4(prjFile, wgs84prjString,value)); 161 | }); 162 | feature.geometry.coordinates = newCoordinates; 163 | } else if(feature.geometry.type == "MultiLineString" || feature.geometry.type == "Polygon"){ 164 | let newCoordinatesList: any[][] = []; 165 | feature.geometry.coordinates.forEach(function (coordinateList: number[][]) { 166 | let newCoordinates: any[] = []; 167 | newCoordinatesList.push(newCoordinates); 168 | coordinateList.forEach(function (value: number[]) { 169 | newCoordinates.push(proj4(prjFile, wgs84prjString,value)); 170 | }); 171 | }); 172 | feature.geometry.coordinates = newCoordinatesList; 173 | } else if(feature.geometry.type == "MultiPolygon"){ 174 | let newCoordinatesListArray: any[][][] = []; 175 | feature.geometry.coordinates.forEach(function (coordinateListArray: number[][][]) { 176 | let newCoordinatesList: any[][] = []; 177 | newCoordinatesListArray.push(newCoordinatesList); 178 | coordinateListArray.forEach(function (coordinateList: number[][]) { 179 | let newCoordinates: any[] = []; 180 | newCoordinatesList.push(newCoordinates); 181 | coordinateList.forEach(function (value: number[]) { 182 | newCoordinates.push(proj4(prjFile, wgs84prjString,value)); 183 | }); 184 | }); 185 | }); 186 | feature.geometry.coordinates = newCoordinatesListArray; 187 | } else { 188 | console.log("Unsupported Geometry type - " + feature.geometry.type); 189 | process.exit(1); 190 | } 191 | return feature; 192 | } 193 | 194 | export async function read(path: string, needConversion: boolean, opt: any = null) { 195 | if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) { 196 | return await readDataFromURL(path, needConversion, opt); 197 | } else { 198 | return await readDataFromFile(path, needConversion, opt); 199 | } 200 | } 201 | 202 | async function readDataFromURL(path: string, needConversion: boolean, opt: any = null) { 203 | const response = await requestAsync({ url: path }); 204 | if (response.statusCode != 200) 205 | throw new Error("Error requesting: " + response.body); 206 | 207 | if (needConversion) 208 | return await dataToJson(response.body, opt); 209 | else 210 | return response.body; 211 | } 212 | 213 | async function readDataFromFile(path: string, needConversion: boolean, opt: any = null) { 214 | const file_data = fs.readFileSync(path, { encoding: 'utf8' }); 215 | if (needConversion) 216 | return await dataToJson(file_data, opt); 217 | else 218 | return file_data; 219 | } 220 | 221 | async function dataToJson(file_data: string, opt: any = null) { 222 | //const csvjson = require('csvjson'); 223 | //const result = csvjson.toObject(file_data, opt); 224 | const result = await parseCsv(file_data, opt); 225 | return result; 226 | } 227 | 228 | async function parseCsv(csvStr: string, options: any) { 229 | return new Promise((res, rej) => { 230 | const rows:any[] = []; 231 | csv.parseString(csvStr, options) 232 | .on('data', (row: any) => rows.push(row)) 233 | .on('error', (err: any) => rej(err)) 234 | .on('end', () => res(rows)); 235 | }); 236 | } 237 | 238 | export async function getFirstNRowsOfCsv(options: any, numberOfRows: number){ 239 | return new Promise((res, rej) => { 240 | var stream = fs.createReadStream(options.file); 241 | let csvStream = csv.parseStream(stream, {headers : true, delimiter: options.delimiter, quote: options.quote}); 242 | //.on("data", async function(data:any){ 243 | 244 | var rows: any[] = []; 245 | var onData = function(row: any){ 246 | rows.push(row); 247 | if (rows.length == numberOfRows) { 248 | csvStream.emit('donereading'); //custom event for convenience 249 | } 250 | }; 251 | csvStream.on('data', onData); 252 | csvStream.on('donereading', function(){ 253 | stream.close(); 254 | csvStream.removeListener('data', onData); 255 | res(rows); 256 | }); 257 | csvStream.on("end", function(){ 258 | res(rows); 259 | }); 260 | csvStream.on('error', (err: any) => rej(err)); 261 | }); 262 | } 263 | 264 | async function getGpxDataFromXmlNode(node: any, result: any) { 265 | if (!result) result = { segments: [] } 266 | switch (node.nodeName) { 267 | case 'name': 268 | //console.log(node.nodeName + ' = ' + node.textContent) 269 | result.name = node.textContent 270 | break 271 | case 'trkseg': 272 | let segment = [] as any 273 | result.segments.push(segment) 274 | let len = node && node.childNodes && node.childNodes.length 275 | for (var i = 0; i < len; i++) { 276 | var snode = node.childNodes[i] 277 | if (snode.nodeName == 'trkpt') { 278 | let trkpt: any = {} 279 | //console.log("ATTR:", snode.attributes["0"].value) 280 | let lat = snode && snode.attributes && snode.attributes['0'] && snode.attributes['0'].value 281 | let lon = snode && snode.attributes && snode.attributes['1'] && snode.attributes['1'].value 282 | trkpt = { 283 | loc: [ 284 | parseFloat(lat), 285 | parseFloat(lon) 286 | ] 287 | } 288 | 289 | let len = snode && snode.childNodes && snode.childNodes.length 290 | for (var j = 0; j < len; j++) { 291 | var ssnode = snode.childNodes[j] 292 | switch (ssnode.nodeName) { 293 | case 'time': 294 | trkpt.time = new Date(ssnode.childNodes[0].data) 295 | break 296 | case 'ele': 297 | trkpt.ele = parseFloat(ssnode.childNodes[0].data) 298 | break 299 | case 'extensions': 300 | var extNodes = ssnode.childNodes 301 | for ( var idxExtNode = 0; idxExtNode < extNodes.length; idxExtNode++ ) { 302 | var extNode = extNodes[idxExtNode] 303 | //console.log(extNode.nodeName) 304 | if (extNode.nodeName == 'gpxtpx:TrackPointExtension') { 305 | //console.log(extNode) 306 | var trackPointNodes = extNode.childNodes 307 | for ( var idxTrackPointNode = 0; idxTrackPointNode < trackPointNodes.length; idxTrackPointNode++ ) { 308 | var trackPointNode = 309 | trackPointNodes[idxTrackPointNode] 310 | //console.log(trackPointNode.nodeName) 311 | if (trackPointNode.nodeName.startsWith('gpxtpx:')) { 312 | var gpxName = trackPointNode.nodeName.split(':') 313 | 314 | trkpt[gpxName[1]] = 315 | trackPointNode.childNodes[0].data 316 | } 317 | } 318 | } 319 | } 320 | //console.log(ssnode.childNodes) 321 | //extNode.forEach(element => { 322 | //console.log(element.power) 323 | //}) 324 | break 325 | } 326 | } 327 | //console.log("trkpt", trkpt) 328 | segment.push(trkpt) 329 | } 330 | } 331 | break 332 | } 333 | let len = node && node.childNodes && node.childNodes.length 334 | for ( var idxChildNodes = 0; idxChildNodes < len; idxChildNodes++ ) { 335 | getGpxDataFromXmlNode(node.childNodes[idxChildNodes], result) 336 | } 337 | return result 338 | } 339 | 340 | 341 | async function trasformGpxDataToGeoJson(data: any) { 342 | let geo: any = {}; 343 | geo.type = 'FeatureCollection' 344 | geo.features = [] 345 | if (data && data.segments) { 346 | let prev_position_long = 0 347 | let prev_position_lat = 0 348 | let idx_records = 0 349 | let element: any = {} 350 | for ( idx_records = 0; idx_records < data.segments[0].length; idx_records++ ) { 351 | element = data.segments[0][idx_records] 352 | if (Array.isArray(element.loc)) { 353 | if (idx_records > 0) { 354 | let f: any = {} 355 | f.type = 'Feature' 356 | f.properties = element 357 | f.geometry = {} 358 | f.geometry.type = 'LineString' 359 | f.geometry.coordinates = [ 360 | [prev_position_long, prev_position_lat], 361 | [element.loc[1], element.loc[0]] 362 | ] 363 | geo.features.push(f) 364 | } 365 | prev_position_long = element.loc[1] 366 | prev_position_lat = element.loc[0] 367 | } 368 | } 369 | } 370 | return geo.features 371 | } 372 | export async function transformGpx(result: any[], options: any) { 373 | const xml = new DOMParser().parseFromString(String(result), 'text/xml') 374 | var objGpx = await getGpxDataFromXmlNode(xml.documentElement, false) 375 | return await trasformGpxDataToGeoJson(objGpx) 376 | } 377 | 378 | export async function transform(result: any[], options: any) { 379 | const objects: Map = new Map(); 380 | if(options.assign && result.length > 0){ 381 | await setStringFieldsFromUser(result[0],options); 382 | } 383 | if(!options.stream){ 384 | await toGeoJsonFeature(result[0], options, true);//calling this to ask Lat Lon question to the user for only one time 385 | } 386 | for (const i in result) { 387 | const ggson = await toGeoJsonFeature(result[i], options, false); 388 | if (ggson) { 389 | if(options.keys){ 390 | const propertyValue = ggson.properties[options.csvProperty]; 391 | if(joinValueToFeatureIdMap.has(propertyValue)){ 392 | ggson.properties['id'] = joinValueToFeatureIdMap.get(propertyValue); 393 | ggson['id'] = joinValueToFeatureIdMap.get(propertyValue); 394 | result[i]['id'] = joinValueToFeatureIdMap.get(propertyValue); 395 | if(!joinValueToFeatureIdMap.get(propertyValue)){ 396 | if(!ggson.properties["@ns:com:here:xyz"]) { 397 | ggson.properties["@ns:com:here:xyz"] = {}; 398 | } 399 | if(!ggson.properties["@ns:com:here:xyz"]["tags"]) { 400 | ggson.properties["@ns:com:here:xyz"]["tags"] = []; 401 | } 402 | ggson.properties["@ns:com:here:xyz"]["tags"].push("no_match"); 403 | } 404 | } else { 405 | options.search = "p." + options.spaceProperty + "='" + propertyValue + "'"; 406 | if(options.filter){ 407 | options.search = options.search + '&p.' + options.filter; 408 | } 409 | let jsonOut = await getSpaceDataFromXyz(options.primarySpace, options); 410 | if (jsonOut.features && jsonOut.features.length === 0) { 411 | console.log("\nNo feature available for the required value - " + propertyValue); 412 | if(!ggson.properties["@ns:com:here:xyz"]) { 413 | ggson.properties["@ns:com:here:xyz"] = {}; 414 | } 415 | if(!ggson.properties["@ns:com:here:xyz"]["tags"]) { 416 | ggson.properties["@ns:com:here:xyz"]["tags"] = []; 417 | } 418 | ggson.properties["@ns:com:here:xyz"]["tags"].push("no_match"); 419 | } else { 420 | ggson.properties['id'] = jsonOut.features[0].id; 421 | ggson['id'] = jsonOut.features[0].id; 422 | result[i]['id'] = jsonOut.features[0].id; 423 | } 424 | joinValueToFeatureIdMap.set(propertyValue, result[i]['id']); 425 | } 426 | console.log("featureId for property " + propertyValue + " is - " + result[i]['id']); 427 | } 428 | if(options.groupby){ 429 | let key = null; 430 | if(options.id){ 431 | key = common.createUniqueId(options.id,ggson); 432 | } else { 433 | key = result[i]['id']; 434 | } 435 | if(!key && !options.keys){ 436 | console.log("'groupby' option requires 'id' field and id is not present in record - " + JSON.stringify(ggson)); 437 | process.exit(1); 438 | } 439 | let value: any = {}; 440 | let properties: any; 441 | if(objects.get(key)){ 442 | value = objects.get(key); 443 | properties = ggson.properties; 444 | } else { 445 | properties = ggson.properties; 446 | value = ggson; 447 | delete value.properties; 448 | value.properties = {}; 449 | if(options.id){ 450 | value.properties[options.id] = properties[options.id]; 451 | } else { 452 | value.properties['id'] = properties['id']; 453 | } 454 | value.properties["@ns:com:here:xyz"] = properties["@ns:com:here:xyz"]; 455 | if(!options.flatten){ 456 | value.properties[options.groupby] = {}; 457 | } 458 | objects.set(key,value); 459 | } 460 | delete properties[options.groupby]; 461 | delete properties[options.id]; 462 | delete properties["@ns:com:here:xyz"]; 463 | if(options.promote){ 464 | options.promote.split(',').forEach((key: string) => { 465 | value.properties[key] = properties[key]; 466 | delete properties[key]; 467 | }); 468 | } 469 | if(options.flatten){ 470 | Object.keys(properties).forEach(key => { 471 | value.properties[options.groupby + ":" + result[i][options.groupby] + ":" + key] = properties[key]; 472 | }); 473 | } else { 474 | value.properties[options.groupby][result[i][options.groupby]] = properties; 475 | } 476 | } else { 477 | objects.set(i,ggson); 478 | } 479 | } 480 | } 481 | return Array.from(objects.values()); 482 | } 483 | 484 | async function setStringFieldsFromUser(object:any, options: any){ 485 | let choiceList = createQuestionsList(object); 486 | const stringFieldQuestion = [ 487 | { 488 | type: "checkbox", 489 | name: "stringFieldChoice", 490 | message: 491 | "Select attributes which should be stored as String even though they are numbers/boolean (especially where leading zeros are important e.g. postal codes, FIPS codes)", 492 | choices: choiceList 493 | } 494 | ]; 495 | let answers: any = await inquirer.prompt(stringFieldQuestion); 496 | if (options.stringFields === undefined || options.stringFields == '') { 497 | options.stringFields = ""; 498 | } else { 499 | options.stringFields = options.stringFields + ","; 500 | } 501 | options.stringFields = options.stringFields + answers.stringFieldChoice; 502 | } 503 | 504 | async function toGeoJsonFeature(object: any, options: any, isAskQuestion: boolean = false) { 505 | //latField: string, lonField: string, altField: string, pointField: string, stringFields: string = '') { 506 | const props: any = {}; 507 | let lat = undefined; 508 | let lon = undefined; 509 | let alt = undefined; 510 | for (const k in object) { 511 | let key = k.trim(); 512 | if (key == options.point) { // we shouldn't automatically look for a field called points 513 | //console.log('extracting lat/lon from',pointField,object[k]) 514 | const point = object[k] ? object[k].match(/([-]?\d+[.]?\d*)/g) : null; 515 | if(point) { 516 | if(options.lonlat){ 517 | lat = point[1]; 518 | lon = point[0]; 519 | } else { 520 | lat = point[0]; 521 | lon = point[1]; 522 | } 523 | } 524 | }else if (options.lon && options.lon.toLowerCase() == k.toLowerCase()) { 525 | lon = object[k]; 526 | } else if (options.lat && options.lat.toLowerCase() == k.toLowerCase()) { 527 | lat = object[k]; 528 | } else if (options.alt && options.alt.toLowerCase() == k.toLowerCase()) { 529 | alt = object[k]; 530 | } else if (!options.lat && isLat(key)) { 531 | lat = object[k]; 532 | } else if (!options.lon && isLon(key)) { 533 | lon = object[k]; 534 | } else if (!options.alt && isAlt(key)) { 535 | alt = object[k]; 536 | } else { 537 | if(!(options.stringFields && options.stringFields.split(",").includes(k)) && isNumeric(object[k])){ 538 | props[key] = parseFloat(object[k]); 539 | } else if(!(options.stringFields && options.stringFields.split(",").includes(k)) && object[k] && isBoolean(object[k].trim())){ 540 | props[key] = object[k] ? (object[k].trim().toLowerCase() == 'true' ? true : false) : null; 541 | } else { 542 | props[key] = object[k] ? object[k].trim() : null; 543 | } 544 | } 545 | } 546 | if (isAskQuestion) { 547 | if (!options.noCoords && !options.geocode) { 548 | if(lat == null || isNaN(parseFloat(lat))){ 549 | let choiceList = createQuestionsList(object); 550 | const questions = [ 551 | { 552 | type: "list", 553 | name: "latChoice", 554 | message: "Select property which should be be used for Latitude", 555 | choices: choiceList 556 | } 557 | ]; 558 | let latAnswer : any = await inquirer.prompt(questions); 559 | console.log("new Latitude field selected - " + latAnswer.latChoice); 560 | options.lat = latAnswer.latChoice; 561 | lat = object[options.lat]; 562 | } 563 | if(lon == null || isNaN(parseFloat(lon))){ 564 | let choiceList = createQuestionsList(object); 565 | const questions = [ 566 | { 567 | type: "list", 568 | name: "lonChoice", 569 | message: "Select property which should be be used for Longitude", 570 | choices: choiceList 571 | } 572 | ]; 573 | let lonAnswer : any = await inquirer.prompt(questions); 574 | console.log("new Longitude field selected - " + lonAnswer.lonChoice); 575 | options.lon = lonAnswer.lonChoice; 576 | lon = object[options.lon]; 577 | } 578 | } 579 | if(options.askUserForId && !options.id && !object['id'] && !options.keys){ 580 | let choiceList = createQuestionsList(object); 581 | const questions = [ 582 | { 583 | type: "list", 584 | name: "idChoice", 585 | message: "Select property which should be used as featureID", 586 | choices: choiceList 587 | } 588 | ]; 589 | let idAnswer : any = await inquirer.prompt(questions); 590 | console.log("new featureID field selected - " + idAnswer.idChoice); 591 | options.id = idAnswer.idChoice; 592 | } 593 | if(options.groupby && !options.id && !object['id'] && !options.keys){ 594 | console.log("'groupby' option requires 'id' field to be defined in csv"); 595 | process.exit(1); 596 | } 597 | } 598 | 599 | const geometry = toGeometry(lat, lon, alt); 600 | if(geometry == null && !options.geocode){ 601 | props["@ns:com:here:xyz"]={}; 602 | if(lat == null || lat == '' || parseFloat(lat) == 0 || lon == null || lon == '' || parseFloat(lon) == 0){ 603 | props["@ns:com:here:xyz"]["tags"] = ['null_island']; 604 | } else { 605 | props["@ns:com:here:xyz"]["tags"] = ['invalid']; 606 | } 607 | } 608 | return { type: "Feature", geometry: geometry, properties: props, id: object['id']}; 609 | } 610 | 611 | function createQuestionsList(object: any) { 612 | let choiceList: { name: string, value: string }[] = []; 613 | for (const k in object) { 614 | choiceList.push({ name: k + ' : ' + object[k], value: k }); 615 | } 616 | return choiceList; 617 | } 618 | 619 | function isNumeric(n: string) { 620 | return !isNaN(Number(n)) && !isNaN(parseFloat(n)) && isFinite(parseFloat(n)); 621 | } 622 | 623 | function isBoolean(n: string) { 624 | return n.toLowerCase() == 'true' || n.toLowerCase() == 'false'; 625 | } 626 | 627 | function toGeometry(lat: string, lon: string, alt?: string | undefined) { 628 | const latitude = parseFloat(lat); 629 | const longitude = parseFloat(lon); 630 | const altitude = alt ? parseFloat(alt) : undefined; 631 | if((isNaN(latitude) || latitude == 0) && (isNaN(longitude) || longitude == 0)) { 632 | return null; 633 | } 634 | return toPoint(latitude, longitude, altitude); 635 | } 636 | 637 | function toPoint(latitude: number, longitude: number, altitude?: number | undefined) { 638 | const coordinates = (altitude) ? [longitude, latitude, altitude] : [longitude, latitude]; 639 | return { 640 | "type": "Point", 641 | "coordinates": coordinates 642 | }; 643 | } 644 | 645 | function isLat(k: string) { 646 | return latArray.includes(k.toLowerCase()); 647 | } 648 | 649 | function isAlt(k: string) { 650 | return altArray.includes(k.toLowerCase()); 651 | } 652 | 653 | function isLon(k: string) { 654 | return lonArray.includes(k.toLowerCase()); 655 | } 656 | 657 | function isPoint(k: string) { 658 | return pointArray.includes(k.toLowerCase()) 659 | } 660 | 661 | function readData(path: string, postfix: string): Promise { 662 | return new Promise((resolve, reject) => { 663 | if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) { 664 | tmp.file({ mode: 0o644, prefix: '', postfix: postfix }, (err, tempFilePath, fd) => { 665 | if (err) 666 | reject(err); 667 | const dest = fs.createWriteStream(tempFilePath); 668 | dest.on('finish', function (e: any) { 669 | resolve(tempFilePath); 670 | }); 671 | got.stream(path) 672 | .on('error', (err: any) => reject(err)) 673 | .pipe(dest); 674 | }); 675 | } else { 676 | resolve(path); 677 | } 678 | }); 679 | } 680 | 681 | /* 682 | chunckSize should be used later to stream data 683 | */ 684 | export function readLineFromFile(incomingPath: string, chunckSize = 100) { 685 | return readData(incomingPath, 'geojsonl').then(path => { 686 | return new Promise((resolve, reject) => { 687 | const dataArray = new Array(); 688 | const instream = fs.createReadStream(path); 689 | const outstream = new (require('stream'))(); 690 | 691 | const rl = readline.createInterface(instream, outstream); 692 | 693 | rl.on('line', (line: string) => dataArray.push(JSON.parse(line))); 694 | rl.on("error", err => reject(err)); 695 | rl.on('close', () => resolve(dataArray)); 696 | }); 697 | }); 698 | } 699 | 700 | 701 | export function readLineAsChunks(incomingPath: string, chunckSize:number, options: any, streamFuntion:Function) { 702 | return readData(incomingPath, 'geojsonl').then(path => { 703 | return new Promise((resolve, reject) => { 704 | let dataArray = new Array(); 705 | var LineByLineReader = require('line-by-line'), 706 | lr = new LineByLineReader(path); 707 | lr.on('error', function (err:any) { 708 | console.log(err); 709 | throw err; 710 | }); 711 | lr.on('line', async function (line:any) { 712 | dataArray.push(JSON.parse(line)); 713 | if(dataArray.length>=chunckSize){ 714 | lr.pause(); 715 | await streamFuntion(dataArray); 716 | lr.resume(); 717 | dataArray=new Array(); 718 | } 719 | }); 720 | lr.on('end', function () { 721 | (async()=>{ 722 | const queue = await streamFuntion(dataArray); 723 | await queue.shutdown(); 724 | options.totalCount = queue.uploadCount; 725 | console.log(""); 726 | resolve(); 727 | })(); 728 | }); 729 | }); 730 | }); 731 | } 732 | 733 | 734 | export function readCSVAsChunks(incomingPath: string, chunckSize:number,options:any, streamFuntion:Function) { 735 | let isQuestionAsked : boolean = false; 736 | return readData(incomingPath, 'csv').then(path => { 737 | return new Promise((resolve, reject) => { 738 | let dataArray = new Array(); 739 | var csv = require("fast-csv"); 740 | var stream = fs.createReadStream(path); 741 | let csvstream = csv.parseStream(stream, {headers : true, delimiter: options.delimiter, quote: options.quote}).on("data", async function(data:any){ 742 | if(!isQuestionAsked){ 743 | csvstream.pause(); 744 | await toGeoJsonFeature(data, options, true);//calling this to ask Lat Lon question to the user for only one time 745 | isQuestionAsked = true; 746 | csvstream.resume(); 747 | } 748 | dataArray.push(data); 749 | if(dataArray.length >=chunckSize){ 750 | //console.log('dataArray '+chunckSize); 751 | csvstream.pause(); 752 | (async()=>{ 753 | await streamFuntion(dataArray); 754 | csvstream.resume(); 755 | dataArray=new Array(); 756 | })(); 757 | } 758 | }).on("end", function(){ 759 | (async()=>{ 760 | const queue = await streamFuntion(dataArray); 761 | await queue.shutdown(); 762 | options.totalCount = queue.uploadCount; 763 | console.log(""); 764 | resolve(); 765 | })(); 766 | }); 767 | }); 768 | }); 769 | } 770 | 771 | 772 | 773 | export function readGeoJsonAsChunks(incomingPath: string, chunckSize:number, options:any, streamFuntion:Function) { 774 | let isGeoJson : boolean = false; 775 | let isQuestionAsked : boolean = false; 776 | return readData(incomingPath, 'geojson').then(path => { 777 | return new Promise((resolve, reject) => { 778 | let dataArray = new Array(); 779 | const JSONStream = require('JSONStream'); 780 | const es = require('event-stream'); 781 | let fileStream = fs.createReadStream(path, {encoding: 'utf8'}); 782 | let stream = fileStream.pipe(JSONStream.parse('features.*')); 783 | stream.pipe(es.through(async function (data:any) { 784 | dataArray.push(data); 785 | if(dataArray.length >=chunckSize){ 786 | isGeoJson = true; 787 | stream.pause(); 788 | fileStream.pause(); 789 | await streamFuntion(dataArray); 790 | dataArray=new Array(); 791 | stream.resume(); 792 | fileStream.resume(); 793 | } 794 | return data; 795 | },function end () { 796 | if(dataArray.length >0){ 797 | isGeoJson = true; 798 | } 799 | (async()=>{ 800 | const queue = await streamFuntion(dataArray); 801 | await queue.shutdown(); 802 | options.totalCount = queue.uploadCount; 803 | console.log(""); 804 | dataArray=new Array(); 805 | resolve(); 806 | })(); 807 | 808 | if(!isGeoJson){ 809 | fileStream = fs.createReadStream(path, {encoding: 'utf8'}); 810 | stream = fileStream.pipe(JSONStream.parse('*')); 811 | stream.pipe(es.through(async function (data:any) { 812 | if(!isQuestionAsked){ 813 | stream.pause(); 814 | fileStream.pause(); 815 | await toGeoJsonFeature(data, options, true);//calling this to ask Lat Lon question to the user for only one time 816 | isQuestionAsked = true; 817 | stream.resume(); 818 | fileStream.resume(); 819 | } 820 | dataArray.push(data); 821 | if(dataArray.length >=chunckSize){ 822 | stream.pause(); 823 | fileStream.pause(); 824 | dataArray = await transform(dataArray, options); 825 | await streamFuntion(dataArray); 826 | dataArray=new Array(); 827 | stream.resume(); 828 | fileStream.resume(); 829 | } 830 | return data; 831 | },function end () { 832 | (async()=>{ 833 | if(dataArray.length >0){ 834 | dataArray = await transform(dataArray, options); 835 | } 836 | const queue = await streamFuntion(dataArray); 837 | await queue.shutdown(); 838 | options.totalCount = queue.uploadCount; 839 | console.log(""); 840 | dataArray=new Array(); 841 | resolve(); 842 | })(); 843 | })); 844 | } 845 | })); 846 | 847 | }); 848 | }); 849 | } 850 | 851 | -------------------------------------------------------------------------------- /test/data/sample.csv: -------------------------------------------------------------------------------- 1 | id,lat,lon,name 2 | 10,10,10,testing -------------------------------------------------------------------------------- /test/data/sample.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Point", 9 | "coordinates": [ 10 | -4.187271595001221, 11 | 40.95480471183075 12 | ] 13 | } 14 | }, 15 | { 16 | "type": "Feature", 17 | "properties": {}, 18 | "geometry": { 19 | "type": "Point", 20 | "coordinates": [ 21 | -4.186810255050659, 22 | 40.954148389459085 23 | ] 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /test/data/sample.geojsonl: -------------------------------------------------------------------------------- 1 | {"type": "Feature","properties": {},"geometry": {"type": "Point","coordinates": [-4.187271595001221,40.95480471183075]}} 2 | {"type": "Feature","properties": {},"geometry": {"type": "Point","coordinates": [-4.187271595001222,40.95480471183075]}} -------------------------------------------------------------------------------- /test/data/shapesample/shapesample.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heremaps/here-cli/6a76fd609515f393ae47506ed06f9a10216bef17/test/data/shapesample/shapesample.dbf -------------------------------------------------------------------------------- /test/data/shapesample/shapesample.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/data/shapesample/shapesample.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heremaps/here-cli/6a76fd609515f393ae47506ed06f9a10216bef17/test/data/shapesample/shapesample.shp -------------------------------------------------------------------------------- /test/data/shapesample/shapesample.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heremaps/here-cli/6a76fd609515f393ae47506ed06f9a10216bef17/test/data/shapesample/shapesample.shx -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 - 2021 HERE Europe B.V. 3 | SPDX-License-Identifier: MIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | const express = require('express') 26 | const app = express() 27 | const port = 3578; 28 | 29 | app.get('/', (req, res) => res.send('Hello World!')); 30 | 31 | app.post("/token-api/tokens", function(req, res) { 32 | res.send({}); 33 | }); 34 | app.get("/token-api/tokens", function(req, res) { 35 | res.send({tokens:[{id:"a",type:"b",iat:"c",description:"d"}]}); 36 | }); 37 | app.get("/hub/spaces", function(req, res) { 38 | res.send([{"id":"oQ8SICzO","title":"a new Data Hub space created from commandline","description":"a new Data Hub space created from commandline","owner":"eLmjOcdwpIk6Svi51Lah"}]); 39 | }); 40 | app.get("/hub/spaces/:spaceId", function(req, res) { 41 | res.send({"id":"oQ8SICzO","title":"a new Data Hub space created from commandline","description":"a new Data Hub space created from commandline","owner":"eLmjOcdwpIk6Svi51Lah"}); 42 | }); 43 | app.get("/hub/spaces/:spaceId/iterate", function(req, res) { 44 | res.send({"type":"FeatureCollection","etag":"866d7c6581589e37","streamId":"38387417-3102-11e9-baea-7bdf3a786698","features":[{"id":"9376020521","bbox":[0.21306,47.97855,0.21336,47.97901],"type":"Feature","properties":{"ruleId":"PAD030","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@pad030","isocode@fra","groupkey@fra;quarterly;pad","program@pad","cadence@quarterly"],"space":"GZtULMpb","createdAt":1550048437484,"updatedAt":1550048437484},"@ns:com:here:rmob":{"fc":3,"linkId":"1814549030","adminId":"241715910","isUrban":true,"groupKey":"FRA;QUARTERLY;PAD","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"FRA"}},"geometry":{"type":"MultiLineString","coordinates":[[[0.21336,47.97855,0.0],[0.21331,47.97861,0.0],[0.21326,47.97867,0.0],[0.21322,47.97872,0.0],[0.21317,47.97881,0.0],[0.21306,47.97901,0.0]]]}},{"id":"9376028927","bbox":[5.93369,50.75031,5.93369,50.75076],"type":"Feature","properties":{"ruleId":"POI017","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@poi017","isocode@bel","groupkey@bel;daily;poi","program@poi","cadence@daily"],"space":"GZtULMpb","createdAt":1550048437484,"updatedAt":1550048437484},"@ns:com:here:rmob":{"fc":5,"linkId":"8060153021","adminId":"139531018","isUrban":true,"groupKey":"BEL;DAILY;POI","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"BEL"}},"geometry":{"type":"MultiLineString","coordinates":[[[5.93369,50.75031,0.0],[5.93369,50.75076,0.0]]]}},{"id":"9376048656","bbox":[11.56092,48.13991,11.56099,48.14032],"type":"Feature","properties":{"ruleId":"POI251","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@poi251","isocode@deu","groupkey@deu;weekly;poi","program@poi","cadence@weekly"],"space":"GZtULMpb","createdAt":1550048437509,"updatedAt":1550048437509},"@ns:com:here:rmob":{"fc":3,"linkId":"155987012","adminId":"150749781","isUrban":true,"groupKey":"DEU;WEEKLY;POI","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"DEU"}},"geometry":{"type":"MultiLineString","coordinates":[[[11.56092,48.13991,0.0],[11.56094,48.13999,0.0],[11.56099,48.14032,0.0]]]}},{"id":"9375798871","bbox":[-6.02509,54.55626,-6.02461,54.55636],"type":"Feature","properties":{"ruleId":"ADD029","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@add029","isocode@gbr","groupkey@gbr;quarterly;add","program@add","cadence@quarterly"],"space":"GZtULMpb","createdAt":1550048437509,"updatedAt":1550048437509},"@ns:com:here:rmob":{"fc":5,"linkId":"458203148","adminId":"457765392","isUrban":true,"groupKey":"GBR;QUARTERLY;ADD","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"GBR"}},"geometry":{"type":"MultiLineString","coordinates":[[[-6.02461,54.55626,0.0],[-6.0248,54.55633,0.0],[-6.02492,54.55636,0.0],[-6.02499,54.55635,0.0],[-6.02509,54.55627,0.0]],[[-6.02461,54.55626,0.0],[-6.0248,54.55633,0.0],[-6.02492,54.55636,0.0],[-6.02499,54.55635,0.0],[-6.02509,54.55627,0.0]],[[-6.02461,54.55626,0.0],[-6.0248,54.55633,0.0],[-6.02492,54.55636,0.0],[-6.02499,54.55635,0.0],[-6.02509,54.55627,0.0]],[[-6.02461,54.55626,0.0],[-6.0248,54.55633,0.0],[-6.02492,54.55636,0.0],[-6.02499,54.55635,0.0],[-6.02509,54.55627,0.0]]]}},{"id":"9375841173","bbox":[-6.00722,54.54558,-6.00697,54.5458],"type":"Feature","properties":{"ruleId":"ADD029","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@add029","isocode@gbr","groupkey@gbr;quarterly;add","program@add","cadence@quarterly"],"space":"GZtULMpb","createdAt":1550048437509,"updatedAt":1550048437509},"@ns:com:here:rmob":{"fc":5,"linkId":"1053922551","adminId":"457765392","isUrban":true,"groupKey":"GBR;QUARTERLY;ADD","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"GBR"}},"geometry":{"type":"MultiLineString","coordinates":[[[-6.00722,54.54558,0.0],[-6.00712,54.54567,0.0],[-6.00697,54.5458,0.0]],[[-6.00722,54.54558,0.0],[-6.00712,54.54567,0.0],[-6.00697,54.5458,0.0]],[[-6.00722,54.54558,0.0],[-6.00712,54.54567,0.0],[-6.00697,54.5458,0.0]],[[-6.00722,54.54558,0.0],[-6.00712,54.54567,0.0],[-6.00697,54.5458,0.0]]]}}]}); 45 | }); 46 | app.get("/hub/spaces/:spaceId/search", function(req, res) { 47 | res.send({"type":"FeatureCollection","etag":"866d7c6581589e37","streamId":"38387417-3102-11e9-baea-7bdf3a786698","features":[{"id":"9376020521","bbox":[0.21306,47.97855,0.21336,47.97901],"type":"Feature","properties":{"ruleId":"PAD030","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@pad030","isocode@fra","groupkey@fra;quarterly;pad","program@pad","cadence@quarterly"],"space":"GZtULMpb","createdAt":1550048437484,"updatedAt":1550048437484},"@ns:com:here:rmob":{"fc":3,"linkId":"1814549030","adminId":"241715910","isUrban":true,"groupKey":"FRA;QUARTERLY;PAD","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"FRA"}},"geometry":{"type":"MultiLineString","coordinates":[[[0.21336,47.97855,0.0],[0.21331,47.97861,0.0],[0.21326,47.97867,0.0],[0.21322,47.97872,0.0],[0.21317,47.97881,0.0],[0.21306,47.97901,0.0]]]}},{"id":"9376028927","bbox":[5.93369,50.75031,5.93369,50.75076],"type":"Feature","properties":{"ruleId":"POI017","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@poi017","isocode@bel","groupkey@bel;daily;poi","program@poi","cadence@daily"],"space":"GZtULMpb","createdAt":1550048437484,"updatedAt":1550048437484},"@ns:com:here:rmob":{"fc":5,"linkId":"8060153021","adminId":"139531018","isUrban":true,"groupKey":"BEL;DAILY;POI","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"BEL"}},"geometry":{"type":"MultiLineString","coordinates":[[[5.93369,50.75031,0.0],[5.93369,50.75076,0.0]]]}},{"id":"9376048656","bbox":[11.56092,48.13991,11.56099,48.14032],"type":"Feature","properties":{"ruleId":"POI251","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@poi251","isocode@deu","groupkey@deu;weekly;poi","program@poi","cadence@weekly"],"space":"GZtULMpb","createdAt":1550048437509,"updatedAt":1550048437509},"@ns:com:here:rmob":{"fc":3,"linkId":"155987012","adminId":"150749781","isUrban":true,"groupKey":"DEU;WEEKLY;POI","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"DEU"}},"geometry":{"type":"MultiLineString","coordinates":[[[11.56092,48.13991,0.0],[11.56094,48.13999,0.0],[11.56099,48.14032,0.0]]]}},{"id":"9375798871","bbox":[-6.02509,54.55626,-6.02461,54.55636],"type":"Feature","properties":{"ruleId":"ADD029","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@add029","isocode@gbr","groupkey@gbr;quarterly;add","program@add","cadence@quarterly"],"space":"GZtULMpb","createdAt":1550048437509,"updatedAt":1550048437509},"@ns:com:here:rmob":{"fc":5,"linkId":"458203148","adminId":"457765392","isUrban":true,"groupKey":"GBR;QUARTERLY;ADD","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"GBR"}},"geometry":{"type":"MultiLineString","coordinates":[[[-6.02461,54.55626,0.0],[-6.0248,54.55633,0.0],[-6.02492,54.55636,0.0],[-6.02499,54.55635,0.0],[-6.02509,54.55627,0.0]],[[-6.02461,54.55626,0.0],[-6.0248,54.55633,0.0],[-6.02492,54.55636,0.0],[-6.02499,54.55635,0.0],[-6.02509,54.55627,0.0]],[[-6.02461,54.55626,0.0],[-6.0248,54.55633,0.0],[-6.02492,54.55636,0.0],[-6.02499,54.55635,0.0],[-6.02509,54.55627,0.0]],[[-6.02461,54.55626,0.0],[-6.0248,54.55633,0.0],[-6.02492,54.55636,0.0],[-6.02499,54.55635,0.0],[-6.02509,54.55627,0.0]]]}},{"id":"9375841173","bbox":[-6.00722,54.54558,-6.00697,54.5458],"type":"Feature","properties":{"ruleId":"ADD029","references":[],"featureType":"Violation","@ns:com:here:xyz":{"tags":["workspace@weu_bw_1901","active@yes","status@pending","rulecode@add029","isocode@gbr","groupkey@gbr;quarterly;add","program@add","cadence@quarterly"],"space":"GZtULMpb","createdAt":1550048437509,"updatedAt":1550048437509},"@ns:com:here:rmob":{"fc":5,"linkId":"1053922551","adminId":"457765392","isUrban":true,"groupKey":"GBR;QUARTERLY;ADD","leStatus":"Exception","workspace":"WEU_BW_1901"},"@ns:com:here:uom:meta":{"uomVersion":"uom-schema-1.4.1","isoCountryCode":"GBR"}},"geometry":{"type":"MultiLineString","coordinates":[[[-6.00722,54.54558,0.0],[-6.00712,54.54567,0.0],[-6.00697,54.5458,0.0]],[[-6.00722,54.54558,0.0],[-6.00712,54.54567,0.0],[-6.00697,54.5458,0.0]],[[-6.00722,54.54558,0.0],[-6.00712,54.54567,0.0],[-6.00697,54.5458,0.0]],[[-6.00722,54.54558,0.0],[-6.00712,54.54567,0.0],[-6.00697,54.5458,0.0]]]}}]}); 48 | }); 49 | 50 | app.delete("/hub/spaces/:spaceId", function(req, res) { 51 | res.send({}); 52 | }); 53 | app.patch("/hub/spaces/:spaceId", function(req, res) { 54 | res.send({"id":"testing"}); 55 | }); 56 | app.post("/hub/spaces/", function(req, res) { 57 | res.send({"id":"testing"}); 58 | }); 59 | app.put("/hub/spaces/:spaceId/features", function(req, res) { 60 | res.send({}); 61 | }); 62 | app.post("/hub/spaces/:spaceId/features", function(req, res) { 63 | res.send({}); 64 | }); 65 | app.delete("/hub/spaces/:spaceId/features", function(req, res) { 66 | res.send({}); 67 | }); 68 | 69 | var server=null; 70 | exports.listen = function () { 71 | server = app.listen(port,()=>"Started Listening"); 72 | console.log("Server Started on "+port); 73 | } 74 | 75 | exports.close = function (callback) { 76 | server.close(); 77 | }; 78 | //exports.listen(); 79 | //server = app.listen(port,()=>"Started Listening"); 80 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2018 - 2021 HERE Europe B.V. 3 | SPDX-License-Identifier: MIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | var assert = require('assert'); 26 | const expect = require('chai').expect; 27 | // var nrc = require('node-run-cmd'); 28 | var mock = require('mock-require'); 29 | var rewire = require("rewire"); 30 | var server = require('./server'); 31 | var capcon = require('capture-console'); 32 | 33 | 34 | // describe('Installation', function () { 35 | // describe('VersionCheck', function () { 36 | // it('should return 0 as the exit code for version check', async function () { 37 | // this.timeout(10000); 38 | // await nrc.run('chmod 777 ./bin/here.js'); 39 | // const ecodes = await nrc.run('./bin/here.js --version'); 40 | // assert.equal(0, ecodes[0]); 41 | // }); 42 | // }); 43 | // }); 44 | 45 | describe('Configure', function () { 46 | describe('configure', function () { 47 | var count = 0; 48 | before(function () { 49 | console.log("starting server"); 50 | server.listen(); 51 | mock('user-settings', { 52 | file: function (a) { 53 | return { 54 | get: function (keyName) { 55 | if(keyName == 'apiServerUrl'){ 56 | return "http://localhost:3578"; 57 | } 58 | return "testing"; 59 | }, 60 | set: function () { 61 | } 62 | } 63 | } 64 | }); 65 | let option = function () { 66 | return { 67 | option: option, 68 | action: option, 69 | description: option, 70 | alias: option, 71 | command: option, 72 | version: option, 73 | parse: option 74 | }; 75 | } 76 | mock('commander', { 77 | version:option, 78 | help: option, 79 | parse: option, 80 | command: option, 81 | commandHelp: option, 82 | name:option 83 | } 84 | ); 85 | }); 86 | it('should authenticate appid/appcode properly', async function () { 87 | count++; 88 | // mock('prompt', 89 | // { 90 | // get: function(a,b) { 91 | // console.log(b); 92 | // let result = []; 93 | // result['AppId']='test'; 94 | // result['AppCode']='test123'; 95 | // console.log(b(null,result)); 96 | // }, 97 | // start: function() { 98 | // console.log('start called'); 99 | // }, 100 | // stop: function() { 101 | // console.log('end called'); 102 | // } 103 | // }); 104 | //const prompter = require('prompt'); 105 | // const configure = rewire('../bin/here-configure'); 106 | // const setAuth = configure.__get__('setAuth'); 107 | // console.log(setAuth({})); 108 | const common = rewire('../bin/common'); 109 | common.__set__('xyzRoot', (boolean) => "http://localhost:3578"); 110 | const { esponse, authId, authSecret } = await common.login("abcd", "secret"); 111 | assert.equal("abcd", authId); 112 | assert.equal("secret", authSecret); 113 | count--; 114 | }); 115 | 116 | it('should authenticate userName/password properly', async function () { 117 | count++; 118 | const common = rewire('../bin/common'); 119 | common.__get__('sso').executeWithCookie = function () { 120 | return new Promise((res, rej) => { 121 | res("myCookie"); 122 | }) 123 | } 124 | const token = await common.hereAccountLogin("abcd", "secret"); 125 | assert.equal("myCookie", token); 126 | count--; 127 | }); 128 | 129 | it('list all spaces', async function () { 130 | //sh.exec('chmod 777 ./bin/here.js'); 131 | 132 | const xyz = rewire('../bin/here-xyz'); 133 | xyz.__get__('common').verify=function(){ 134 | return "testtoken"; 135 | } 136 | var output = ''; 137 | capcon.startCapture(process.stdout, function (stdout) { 138 | output += stdout; 139 | }); 140 | await xyz.__get__('listSpaces')({ raw: false, prop: [] }); 141 | capcon.stopCapture(process.stdout); 142 | if (output.indexOf("oQ8SICzO") != -1) { 143 | assert.ok(true, ""); 144 | } else { 145 | assert.fail(); 146 | } 147 | }); 148 | 149 | it('describe space', async function () { 150 | const xyz = rewire('../bin/xyzutil'); 151 | const summary = rewire('../bin/summary'); 152 | var output = ''; 153 | capcon.startCapture(process.stdout, function (stdout) { 154 | output += stdout; 155 | }); 156 | let features = await xyz.__get__('getSpaceDataFromXyz')("myspace", { raw: false, prop: [] }); 157 | 158 | if ( features.type == 'FeatureCollection') { 159 | features = features.features; 160 | } 161 | 162 | summary.summarize(features,"myspace", false, {}); 163 | capcon.stopCapture(process.stdout); 164 | if (features.length>0) { 165 | assert.ok(true, ""); 166 | } else { 167 | assert.fail(); 168 | } 169 | if (output.indexOf("groupkey@bel;daily;poi")!=-1) { 170 | assert.ok(true, ""); 171 | } else { 172 | assert.fail(); 173 | } 174 | }); 175 | 176 | it('analyze space', async function () { 177 | mock('inquirer', { 178 | prompt:function(){ 179 | return new Promise((res,rej)=>{ 180 | res({properties:['ruleId']}); 181 | }); 182 | } 183 | } 184 | ); 185 | const xyz = rewire('../bin/here-xyz'); 186 | var output = ''; 187 | capcon.startCapture(process.stdout, function (stdout) { 188 | output += stdout; 189 | }); 190 | await xyz.__get__('analyzeSpace')("myspace", { raw: false, prop: [] }); 191 | capcon.stopCapture(process.stdout); 192 | if (output.indexOf("ruleId │ ADD029 │ 2")!=-1) { 193 | assert.ok(true, ""); 194 | } else { 195 | assert.fail(); 196 | } 197 | }); 198 | 199 | it('show space', async function () { 200 | const xyzutil = rewire('../bin/xyzutil'); 201 | var output = ''; 202 | capcon.startCapture(process.stdout, function (stdout) { 203 | output += stdout; 204 | }); 205 | await xyzutil.__get__('showSpace')("myspace", { raw: false }); 206 | capcon.stopCapture(process.stdout); 207 | if (output.indexOf("9376020521 │ MultiLineString")!=-1) { 208 | assert.ok(true, ""); 209 | } else { 210 | assert.fail(); 211 | } 212 | }); 213 | it('delete space', async function () { 214 | const xyzutil = rewire('../bin/xyzutil'); 215 | var output = ''; 216 | capcon.startCapture(process.stdout, function (stdout) { 217 | output += stdout; 218 | }); 219 | await xyzutil.__get__('deleteSpace')("myspace", { raw: false, prop: [] , force: true}); 220 | capcon.stopCapture(process.stdout); 221 | if (output.indexOf("Data Hub space 'myspace' deleted successfully")!=-1) { 222 | assert.ok(true, ""); 223 | } else { 224 | assert.fail(); 225 | } 226 | }); 227 | it('create space', async function () { 228 | const xyzutil = rewire('../bin/xyzutil'); 229 | var output = ''; 230 | capcon.startCapture(process.stdout, function (stdout) { 231 | output += stdout; 232 | }); 233 | await xyzutil.__get__('createSpace')({ title: "test", message : "test" }); 234 | capcon.stopCapture(process.stdout); 235 | if (output.indexOf("Data Hub space 'testing' created successfully")!=-1) { 236 | assert.ok(true, ""); 237 | } else { 238 | assert.fail(); 239 | } 240 | }); 241 | it('clear space', async function () { 242 | const xyzutil = rewire('../bin/xyzutil'); 243 | var output = ''; 244 | capcon.startCapture(process.stdout, function (stdout) { 245 | output += stdout; 246 | }); 247 | await xyzutil.__get__('clearSpace')("myspace", { tags:"*" , force: true}); 248 | capcon.stopCapture(process.stdout); 249 | if (output.indexOf("data cleared successfully")!=-1) { 250 | assert.ok(true, ""); 251 | } else { 252 | assert.fail(); 253 | } 254 | }); 255 | 256 | it('list space tokens', async function () { 257 | const xyz = rewire('../bin/here-xyz'); 258 | xyz.__get__('common').decryptAndGet=async function(info, desc){ 259 | console.log("info is"+info); 260 | return "x%%y"; 261 | }; 262 | xyz.__get__('common').getTokenList=async function(){ 263 | return [{tid:"a",type:"b",iat:"c",description:"d"}]; 264 | }; 265 | var output = ''; 266 | capcon.startCapture(process.stdout, function (stdout) { 267 | output += stdout; 268 | }); 269 | await xyz.__get__('listTokens')(); 270 | capcon.stopCapture(process.stdout); 271 | if (output.indexOf("Current CLI token is : x%%y")!=-1) { 272 | assert.ok(true, ""); 273 | } else { 274 | assert.fail(); 275 | } 276 | if (output.indexOf("a │ PERMANENT │ c │ d")!=-1) { 277 | assert.ok(true, ""); 278 | } else { 279 | assert.fail(); 280 | } 281 | }); 282 | 283 | it('upload to space using geojson', async function () { 284 | const xyzutil = rewire('../bin/xyzutil'); 285 | var output = ''; 286 | capcon.startCapture(process.stdout, function (stdout) { 287 | output += stdout; 288 | }); 289 | await xyzutil.__get__('uploadToXyzSpace')("myspace", { file: "test/data/sample.geojson"}); 290 | capcon.stopCapture(process.stdout); 291 | if (output.indexOf("data upload to Data Hub space 'myspace' completed")!=-1) { 292 | assert.ok(true, ""); 293 | } else { 294 | assert.fail(); 295 | } 296 | 297 | if (output.indexOf("Unique tag list :[\"sample\"]")!=-1) { 298 | assert.ok(true, ""); 299 | } else { 300 | assert.fail(); 301 | } 302 | }); 303 | 304 | 305 | 306 | it('upload to space using csv', async function () { 307 | const xyzutil = rewire('../bin/xyzutil'); 308 | var output = ''; 309 | capcon.startCapture(process.stdout, function (stdout) { 310 | output += stdout; 311 | }); 312 | await xyzutil.__get__('uploadToXyzSpace')("myspace", { file: "test/data/sample.csv", delimiter: ',', quote: '"'}); 313 | capcon.stopCapture(process.stdout); 314 | if (output.indexOf("data upload to Data Hub space 'myspace' completed")!=-1) { 315 | assert.ok(true, ""); 316 | } else { 317 | assert.fail(); 318 | } 319 | if (output.indexOf("Unique tag list :[\"sample\"]")!=-1) { 320 | assert.ok(true, ""); 321 | } else { 322 | assert.fail(); 323 | } 324 | if (output.indexOf("sample │ 1")!=-1) { 325 | assert.ok(true, ""); 326 | } else { 327 | assert.fail(); 328 | } 329 | }); 330 | 331 | it('upload to space using gpx', async function () { 332 | const xyzutil = rewire('../bin/xyzutil'); 333 | var output = ''; 334 | capcon.startCapture(process.stdout, function (stdout) { 335 | output += stdout; 336 | }); 337 | await xyzutil.__get__('uploadToXyzSpace')("myspace", { file: "test/data/sample.gpx" }); 338 | capcon.stopCapture(process.stdout); 339 | if (output.indexOf("data upload to Data Hub space 'myspace' completed") != -1) { 340 | assert.ok(true, ""); 341 | } else { 342 | assert.fail(); 343 | } 344 | console.log(output); 345 | if (output.indexOf("1400 features uploaded to Data Hub space 'myspace' in") != -1) { 346 | assert.ok(true, ""); 347 | } else { 348 | assert.fail(); 349 | } 350 | 351 | }); 352 | 353 | it('upload to space using shapefile', async function () { 354 | const xyzutil = rewire('../bin/xyzutil'); 355 | var output = ''; 356 | capcon.startCapture(process.stdout, function (stdout) { 357 | output += stdout; 358 | }); 359 | await xyzutil.__get__('uploadToXyzSpace')("myspace", { file: "test/data/shapesample/shapesample.shp"}); 360 | capcon.stopCapture(process.stdout); 361 | if (output.indexOf("data upload to Data Hub space 'myspace' completed")!=-1) { 362 | assert.ok(true, ""); 363 | } else { 364 | assert.fail(); 365 | } 366 | if (output.indexOf("Unique tag list :[\"shapesample\"]")!=-1) { 367 | assert.ok(true, ""); 368 | } else { 369 | assert.fail(); 370 | } 371 | if (output.indexOf("Total 86 features")!=-1) { 372 | assert.ok(true, ""); 373 | } else { 374 | assert.fail(); 375 | } 376 | }); 377 | 378 | it('upload to space using geojson using stream', async function () { 379 | this.timeout(10000); 380 | const xyzutil = rewire('../bin/xyzutil'); 381 | var output = ''; 382 | capcon.startCapture(process.stdout, function (stdout) { 383 | output += stdout; 384 | }); 385 | await xyzutil.__get__('uploadToXyzSpace')("myspace", { file: "test/data/sample.geojson",stream:true}); 386 | capcon.stopCapture(process.stdout); 387 | 388 | if (output.indexOf("uploaded feature count :0, failed feature count :0")!=-1) { 389 | assert.ok(true, ""); 390 | } else { 391 | assert.fail(); 392 | } 393 | }); 394 | 395 | it('upload to space using csv using stream', async function () { 396 | this.timeout(10000); 397 | const xyzutil = rewire('../bin/xyzutil'); 398 | var output = ''; 399 | capcon.startCapture(process.stdout, function (stdout) { 400 | output += stdout; 401 | }); 402 | await xyzutil.__get__('uploadToXyzSpace')("myspace", { file: "test/data/sample.csv",stream:true, delimiter: ',', quote: '"' }); 403 | capcon.stopCapture(process.stdout); 404 | if (output.indexOf("uploaded feature count :0, failed feature count :0")!=-1) { 405 | assert.ok(true, ""); 406 | } else { 407 | assert.fail(); 408 | } 409 | }); 410 | 411 | it('upload to space using geojsonl', async function () { 412 | this.timeout(10000); 413 | const xyzutil = rewire('../bin/xyzutil'); 414 | var output = ''; 415 | capcon.startCapture(process.stdout, function (stdout) { 416 | output += stdout; 417 | }); 418 | await xyzutil.__get__('uploadToXyzSpace')("myspace", { file: "test/data/sample.geojsonl"}); 419 | capcon.stopCapture(process.stdout); 420 | console.log("output::::"+output); 421 | if (output.indexOf("data upload to Data Hub space 'myspace' completed")!=-1) { 422 | assert.ok(true, ""); 423 | } else { 424 | assert.fail(); 425 | } 426 | }); 427 | 428 | it('upload to space using geojsonl using stream', async function () { 429 | this.timeout(10000); 430 | const xyzutil = rewire('../bin/xyzutil'); 431 | var output = ''; 432 | capcon.startCapture(process.stdout, function (stdout) { 433 | output += stdout; 434 | }); 435 | await xyzutil.__get__('uploadToXyzSpace')("myspace", { file: "test/data/sample.geojsonl",stream:true}); 436 | capcon.stopCapture(process.stdout); 437 | if (output.indexOf("uploaded feature count :0, failed feature count :0")!=-1) { 438 | assert.ok(true, ""); 439 | } else { 440 | assert.fail(); 441 | } 442 | }); 443 | 444 | it('test here geocoder', async function () { 445 | const requestAsync = require('../bin/requestAsync'); 446 | requestAsync.requestAsync=async function(){ 447 | return {statusCode:200,body:JSON.stringify({ "Response": { "MetaInfo": { "Timestamp": "2016-02-15T10:33:55.504+0000" }, "View": [ { "_type": "SearchResultsViewType", "ViewId": 0, "Result": [ { "Relevance": 1, "MatchLevel": "houseNumber", "MatchQuality": { "State": 1, "City": 1, "Street": [ 0.9 ], "HouseNumber": 1 }, "MatchType": "pointAddress", "Location": { "LocationId": "NT_nL.dzNwdSJgdcF4U8dYEiC_yADM", "LocationType": "address", "DisplayPosition": { "Latitude": 37.37634, "Longitude": -122.03405 }, "NavigationPosition": [ { "Latitude": 37.37643, "Longitude": -122.03444 } ], "MapView": { "TopLeft": { "Latitude": 37.3774642, "Longitude": -122.0354646 }, "BottomRight": { "Latitude": 37.3752158, "Longitude": -122.0326354 } }, "Address": { "Label": "200 S Mathilda Ave, Sunnyvale, CA 94086, United States", "Country": "USA", "State": "CA", "County": "Santa Clara", "City": "Sunnyvale", "District": "Heritage District", "Street": "S Mathilda Ave", "HouseNumber": "200", "PostalCode": "94086", "AdditionalData": [ { "value": "United States", "key": "CountryName" }, { "value": "California", "key": "StateName" }, { "value": "Santa Clara", "key": "CountyName" }, { "value": "N", "key": "PostalCodeType" } ] } } } ] } ] } })}; 448 | } 449 | const xyz = rewire('../bin/here-geocode'); 450 | var output = ''; 451 | capcon.startCapture(process.stdout, function (stdout) { 452 | output += stdout; 453 | }); 454 | await xyz.__get__('geoCode')("mumbai"); 455 | capcon.stopCapture(process.stdout); 456 | if (output.indexOf("\"Label\": \"200 S Mathilda Ave, Sunnyvale, CA 94086, United States\"")!=-1) { 457 | assert.ok(true, ""); 458 | } else { 459 | assert.fail(); 460 | } 461 | }); 462 | 463 | 464 | after(async function () { 465 | server.close(); 466 | }) 467 | }); 468 | 469 | }); 470 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "strict": true, 6 | "outDir": "bin", 7 | "baseUrl": "./", 8 | "skipLibCheck":true 9 | } 10 | } 11 | --------------------------------------------------------------------------------