From a2d0e8cf6a9a8fbb31be499588136acf1f201a9c Mon Sep 17 00:00:00 2001 From: evlist Date: Mon, 14 Nov 2022 14:13:12 +0100 Subject: [PATCH] Retrieving GPX import from master. --- package.json | 1 + src/App.tsx | 5 +- src/components/dialogs/GpxImport.module.css | 8 ++ src/components/dialogs/GpxImport.tsx | 54 +++++++++++ src/lib/gpx-parser-builder/LICENSE | 21 +++++ src/lib/gpx-parser-builder/README.md | 91 +++++++++++++++++++ src/lib/gpx-parser-builder/package.json | 36 ++++++++ src/lib/gpx-parser-builder/src/bounds.js | 8 ++ src/lib/gpx-parser-builder/src/copyright.js | 7 ++ .../src/gpx-parser-builder.d.ts | 13 +++ src/lib/gpx-parser-builder/src/gpx.js | 79 ++++++++++++++++ src/lib/gpx-parser-builder/src/link.js | 8 ++ src/lib/gpx-parser-builder/src/metadata.js | 29 ++++++ src/lib/gpx-parser-builder/src/person.js | 11 +++ src/lib/gpx-parser-builder/src/route.js | 27 ++++++ .../gpx-parser-builder/src/track-segment.js | 13 +++ src/lib/gpx-parser-builder/src/track.js | 26 ++++++ src/lib/gpx-parser-builder/src/utils.js | 23 +++++ src/lib/gpx-parser-builder/src/waypoint.js | 32 +++++++ src/missing-typedefs/gpx-parser-builder.d.ts | 1 + 20 files changed, 492 insertions(+), 1 deletion(-) create mode 100644 src/components/dialogs/GpxImport.module.css create mode 100644 src/components/dialogs/GpxImport.tsx create mode 100644 src/lib/gpx-parser-builder/LICENSE create mode 100644 src/lib/gpx-parser-builder/README.md create mode 100644 src/lib/gpx-parser-builder/package.json create mode 100644 src/lib/gpx-parser-builder/src/bounds.js create mode 100644 src/lib/gpx-parser-builder/src/copyright.js create mode 100644 src/lib/gpx-parser-builder/src/gpx-parser-builder.d.ts create mode 100644 src/lib/gpx-parser-builder/src/gpx.js create mode 100644 src/lib/gpx-parser-builder/src/link.js create mode 100644 src/lib/gpx-parser-builder/src/metadata.js create mode 100644 src/lib/gpx-parser-builder/src/person.js create mode 100644 src/lib/gpx-parser-builder/src/route.js create mode 100644 src/lib/gpx-parser-builder/src/track-segment.js create mode 100644 src/lib/gpx-parser-builder/src/track.js create mode 100644 src/lib/gpx-parser-builder/src/utils.js create mode 100644 src/lib/gpx-parser-builder/src/waypoint.js create mode 100644 src/missing-typedefs/gpx-parser-builder.d.ts diff --git a/package.json b/package.json index 8aa04e2..9896588 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@types/react-router": "^5.1.11", "@types/react-router-dom": "^5.1.7", "ionicons": "^6.0.3", + "isomorphic-xml2js": "^0.1.3", "jotai": "^1.8.6", "lodash": "^4.17.21", "pouchdb": "^7.3.0", diff --git a/src/App.tsx b/src/App.tsx index 73751a3..65b20b0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,6 +40,7 @@ import { geoPoint } from './components/map/types'; import Back from './components/buttons/Back'; import Forward from './components/buttons/Forward'; import CurrentLocation from './components/map/CurrentLocation'; +import GpxImport from './components/dialogs/GpxImport'; // import { initDb } from './db'; // import PouchDB from 'pouchdb'; // import PouchDBFind from 'pouchdb-find'; @@ -123,7 +124,9 @@ const App: React.FC = () => { - + + + diff --git a/src/components/dialogs/GpxImport.module.css b/src/components/dialogs/GpxImport.module.css new file mode 100644 index 0000000..440c9c1 --- /dev/null +++ b/src/components/dialogs/GpxImport.module.css @@ -0,0 +1,8 @@ +.inputFile { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} \ No newline at end of file diff --git a/src/components/dialogs/GpxImport.tsx b/src/components/dialogs/GpxImport.tsx new file mode 100644 index 0000000..5a7ca3a --- /dev/null +++ b/src/components/dialogs/GpxImport.tsx @@ -0,0 +1,54 @@ +import React from 'react'; + +import GPX from '../../lib/gpx-parser-builder/src/gpx'; + +import css from './GpxImport.module.css'; +import { IonIcon, IonItem } from '@ionic/react'; +import { cloudUpload } from 'ionicons/icons'; + +const GpxImport: React.FC<{}> = () => { + const onChangeHandler = (event: any) => { + console.log('On change handler'); + const file: File = event.target.files[0]; + const fileReader = new FileReader(); + fileReader.readAsText(file); + + fileReader.addEventListener( + 'load', + () => { + // this will then display a text file + console.log(fileReader.result); + const gpx = GPX.parse(fileReader.result); + console.log(`gpx: ${JSON.stringify(gpx)}`); + // pushGpx(db, { + // gpx, + // metadata: { + // lastModified: new Date(file.lastModified).toISOString(), + // importDate: new Date().toISOString(), + // name: file.name, + // size: file.size, + // type: file.type, + // }, + // }); + }, + false + ); + }; + + return ( + + + + + ); +}; + +export default GpxImport; diff --git a/src/lib/gpx-parser-builder/LICENSE b/src/lib/gpx-parser-builder/LICENSE new file mode 100644 index 0000000..f975d73 --- /dev/null +++ b/src/lib/gpx-parser-builder/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Zheng-Xiang Ke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/lib/gpx-parser-builder/README.md b/src/lib/gpx-parser-builder/README.md new file mode 100644 index 0000000..88d2602 --- /dev/null +++ b/src/lib/gpx-parser-builder/README.md @@ -0,0 +1,91 @@ +# gpx-parser-builder +A simple gpx parser and builder between GPX string and JavaScript object. It is dependent on [isomorphic-xml2js](https://github.com/RikkiGibson/isomorphic-xml2js). + +[![npm](https://img.shields.io/npm/dt/gpx-parser-builder.svg)](https://www.npmjs.com/package/gpx-parser-builder) +[![GitHub stars](https://img.shields.io/github/stars/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/network) +[![npm](https://img.shields.io/npm/v/gpx-parser-builder.svg)](https://www.npmjs.com/package/gpx-parser-builder) +[![GitHub license](https://img.shields.io/github/license/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/blob/master/LICENSE) + +## Requirements + +gpx-parser-builder is written with ECMAScript 6. You can leverage [Babel](https://babeljs.io/) and [Webpack](https://webpack.js.org/) to make all browsers available. + +## Installation + +```bash +npm install gpx-parser-builder --save +``` + +## Version + +v1.0.0+ is a breaking change for v0.2.2-. v1.0.0+ fully supports gpx files including waypoints, routes, and tracks. Every gpx type is 1-1 corresponding to a JavaScript class. + +## Usage + +```javascript +import GPX from 'gpx-parser-builder'; + +// Parse gpx +const gpx = GPX.parse('GPX_STRING'); + +window.console.dir(gpx.metadata); +window.console.dir(gpx.wpt); +window.console.dir(gpx.trk); + +// Build gpx +window.console.log(gpx.toString()); +``` + +Get more details about usage with the unit tests. + +### GPX + +The GPX JavaScript object. + +`constructor(object)` + +```javascript +const gpx = new Gpx({$:{...}, metadat: {...}, wpt:[{...},{...}]}, trk: {...}, rte: {...}) +``` + +#### Member Variables + +`$` the attributes for the gpx element. Default value: +```javascript +{ + 'version': '1.1', + 'creator': 'gpx-parser-builder', + 'xmlns': 'http://www.topografix.com/GPX/1/1', + 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation': 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' +} +``` + +`metadata` the metadata for the gpx. + +`wpt` array of waypoints. It is corresponded to ``. The type of all elements in `wpt` is `Waypoint`; + +`rte` array of routes. It is corresponded to ``. The type of all elements in `rte` is `Route`; + +`trk` array of tracks. It is corresponded to ``. The type of all elements in `trk` is `Track`; + +#### Static Methods + +`parse(gpxString)` parse gpx string to Gpx object. return `null` if parsing failed. + +#### Member Methods + +`toString(options)` GPX object to gpx string. The options is for [isomorphic-xml2js](https://github.com/RikkiGibson/isomorphic-xml2js). + +## Save as GPX file in the frontend + +You can leverage [StreamSaver.js](https://github.com/jimmywarting/StreamSaver.js) or [FileSaver.js](https://github.com/eligrey/FileSaver.js) to save as GPX file. ⚠️Not all borwsers support the above file techniques. ⚠️️️ + +## Author + +Zheng-Xiang Ke, kf99916@gmail.com + +## License + +gpx-parser-builder is available under the MIT license. See the LICENSE file for more info. diff --git a/src/lib/gpx-parser-builder/package.json b/src/lib/gpx-parser-builder/package.json new file mode 100644 index 0000000..19b2cdc --- /dev/null +++ b/src/lib/gpx-parser-builder/package.json @@ -0,0 +1,36 @@ +{ + "name": "gpx-parser-builder", + "version": "1.0.2", + "description": "A simple gpx parser and builder between GPX string and JavaScript object", + "main": "./src/gpx.js", + "scripts": { + "test": "mocha --require @babel/register test/**/*.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/kf99916/gpx-parser-builder.git" + }, + "keywords": [ + "gpx", + "parser", + "builder" + ], + "author": "Zheng-Xiang Ke", + "license": "MIT", + "bugs": { + "url": "https://github.com/kf99916/gpx-parser-builder/issues" + }, + "homepage": "https://github.com/kf99916/gpx-parser-builder", + "files": [ + "src" + ], + "devDependencies": { + "@babel/core": "~7.7", + "@babel/preset-env": "~7.7", + "@babel/register": "~7.7", + "mocha": "~6.2" + }, + "dependencies": { + "isomorphic-xml2js": "~0.1" + } +} diff --git a/src/lib/gpx-parser-builder/src/bounds.js b/src/lib/gpx-parser-builder/src/bounds.js new file mode 100644 index 0000000..08f45b4 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/bounds.js @@ -0,0 +1,8 @@ +export default class Bounds { + constructor(object) { + this.minlat = object.minlat; + this.minlon = object.minlon; + this.maxlat = object.maxlat; + this.maxlon = object.maxlon; + } +} diff --git a/src/lib/gpx-parser-builder/src/copyright.js b/src/lib/gpx-parser-builder/src/copyright.js new file mode 100644 index 0000000..5a9f77e --- /dev/null +++ b/src/lib/gpx-parser-builder/src/copyright.js @@ -0,0 +1,7 @@ +export default class Copyright { + constructor(object) { + this.author = object.author; + this.year = object.year; + this.license = object.license; + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/gpx-parser-builder.d.ts b/src/lib/gpx-parser-builder/src/gpx-parser-builder.d.ts new file mode 100644 index 0000000..37665f0 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/gpx-parser-builder.d.ts @@ -0,0 +1,13 @@ +declare module 'gpx-parser-builder' { + class GPX { + static parse(gpxString: any): any; + constructor(object: any); + $: any; + extensions: any; + metadata: any; + wpt: any; + rte: any; + trk: any; + toString(options: any): string; + } +} diff --git a/src/lib/gpx-parser-builder/src/gpx.js b/src/lib/gpx-parser-builder/src/gpx.js new file mode 100644 index 0000000..07be870 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/gpx.js @@ -0,0 +1,79 @@ +import * as xml2js from 'isomorphic-xml2js'; +import Metadata from './metadata'; +import Waypoint from './waypoint'; +import Route from './route'; +import Track from './track'; +import {removeEmpty, allDatesToISOString} from './utils'; + +const defaultAttributes = { + version: '1.1', + creator: 'gpx-parser-builder', + xmlns: 'http://www.topografix.com/GPX/1/1', + 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation': + 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' +} + +export default class GPX { + constructor(object) { + this.$ = Object.assign({}, defaultAttributes, object.$ || object.attributes || {}); + this.extensions = object.extensions; + + if (object.metadata) { + this.metadata = new Metadata(object.metadata); + } + if (object.wpt) { + if (!Array.isArray(object.wpt)) { + object.wpt = [object.wpt]; + } + this.wpt = object.wpt.map(wpt => new Waypoint(wpt)); + } + if (object.rte) { + if (!Array.isArray(object.rte)) { + object.rte = [object.rte]; + } + this.rte = object.rte.map(rte => new Route(rte)); + } + if (object.trk) { + if (!Array.isArray(object.trk)) { + object.trk = [object.trk]; + } + this.trk = object.trk.map(trk => new Track(trk)); + } + + removeEmpty(this); + } + + static parse(gpxString) { + let gpx; + xml2js.parseString(gpxString, { + explicitArray: false + }, (err, xml) => { + if (err) { + return; + } + if (!xml.gpx) { + return; + } + + gpx = new GPX({ + attributes: xml.gpx.$, + metadata: xml.gpx.metadata, + wpt: xml.gpx.wpt, + rte: xml.gpx.rte, + trk: xml.gpx.trk + }); + }); + + return gpx; + } + + toString(options) { + options = options || {}; + options.rootName = 'gpx'; + + const builder = new xml2js.Builder(options), gpx = new GPX(this); + allDatesToISOString(gpx); + return builder.buildObject(gpx); + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/link.js b/src/lib/gpx-parser-builder/src/link.js new file mode 100644 index 0000000..f7e05af --- /dev/null +++ b/src/lib/gpx-parser-builder/src/link.js @@ -0,0 +1,8 @@ +export default class Link { + constructor(object) { + this.$ = {}; + this.$.href = object.$.href || object.href; + this.text = object.text; + this.type = object.type; + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/metadata.js b/src/lib/gpx-parser-builder/src/metadata.js new file mode 100644 index 0000000..293bbec --- /dev/null +++ b/src/lib/gpx-parser-builder/src/metadata.js @@ -0,0 +1,29 @@ +import Copyright from './copyright'; +import Link from './link'; +import Person from './person'; +import Bounds from './bounds'; + +export default class Metadata { + constructor(object) { + this.name = object.name; + this.desc = object.desc; + this.time = object.time ? new Date(object.time) : new Date(); + this.keywords = object.keywords; + this.extensions = object.extensions; + if (object.author) { + this.author = new Person(object.author); + } + if (object.link) { + if (!Array.isArray(object.link)) { + object.link = [object.link]; + } + this.link = object.link.map(l => new Link(l)); + } + if (object.bounds) { + this.bounds = new Bounds(object.bounds); + } + if (object.copyright) { + this.copyright = new Copyright(object.copyright); + } + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/person.js b/src/lib/gpx-parser-builder/src/person.js new file mode 100644 index 0000000..24b44c4 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/person.js @@ -0,0 +1,11 @@ +import Link from './link'; + +export default class Person { + constructor(object) { + this.name = object.name; + this.email = object.email; + if (object.link) { + this.link = new Link(object.link); + } + } +} diff --git a/src/lib/gpx-parser-builder/src/route.js b/src/lib/gpx-parser-builder/src/route.js new file mode 100644 index 0000000..842e9f8 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/route.js @@ -0,0 +1,27 @@ +import Waypoint from './waypoint'; +import Link from './link'; + +export default class Route { + constructor(object) { + this.name = object.name; + this.cmt = object.cmt; + this.desc = object.desc; + this.src = object.src; + this.number = object.number; + this.type = object.type; + this.extensions = object.extensions; + if (object.link) { + if (!Array.isArray(object.link)) { + this.link = [object.link]; + } + this.link = object.link.map(l => new Link(l)); + } + + if (object.rtept) { + if (!Array.isArray(object.rtept)) { + this.rtept = [object.rtept]; + } + this.rtept = object.rtept.map(pt => new Waypoint(pt)); + } + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/track-segment.js b/src/lib/gpx-parser-builder/src/track-segment.js new file mode 100644 index 0000000..5fca2d5 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/track-segment.js @@ -0,0 +1,13 @@ +import Waypoint from './waypoint'; + +export default class TrackSegment { + constructor(object) { + if (object.trkpt) { + if (!Array.isArray(object.trkpt)) { + object.trkpt = [object.trkpt]; + } + this.trkpt = object.trkpt.map(pt => new Waypoint(pt)); + } + this.extensions = object.extensions; + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/track.js b/src/lib/gpx-parser-builder/src/track.js new file mode 100644 index 0000000..4e330aa --- /dev/null +++ b/src/lib/gpx-parser-builder/src/track.js @@ -0,0 +1,26 @@ +import TrackSegment from './track-segment'; +import Link from './link'; + +export default class Track { + constructor(object) { + this.name = object.name; + this.cmt = object.cmt; + this.desc = object.desc; + this.src = object.src; + this.number = object.number; + this.type = object.type; + this.extensions = object.extensions; + if (object.link) { + if (!Array.isArray(object.link)) { + object.link = [object.link]; + } + this.link = object.link.map(l => new Link(l)); + } + if (object.trkseg) { + if (!Array.isArray(object.trkseg)) { + object.trkseg = [object.trkseg]; + } + this.trkseg = object.trkseg.map(seg => new TrackSegment(seg)); + } + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/utils.js b/src/lib/gpx-parser-builder/src/utils.js new file mode 100644 index 0000000..d769918 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/utils.js @@ -0,0 +1,23 @@ +function removeEmpty(obj) { + Object.entries(obj).forEach(([key, val]) => { + if (val && val instanceof Object) { + removeEmpty(val); + } else if (val == null) { + delete obj[key]; + } + }); +} + +function allDatesToISOString(obj) { + Object.entries(obj).forEach(([key, val]) => { + if (val) { + if (val instanceof Date) { + obj[key] = val.toISOString().split('.')[0] + 'Z'; + } else if (val instanceof Object) { + allDatesToISOString(val); + } + } + }); +} + +export { removeEmpty, allDatesToISOString }; diff --git a/src/lib/gpx-parser-builder/src/waypoint.js b/src/lib/gpx-parser-builder/src/waypoint.js new file mode 100644 index 0000000..2910ff8 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/waypoint.js @@ -0,0 +1,32 @@ +import Link from './link'; + +export default class Waypoint { + constructor(object) { + this.$ = {}; + this.$.lat = object.$.lat === 0 || object.lat === 0 ? 0 : object.$.lat || object.lat || -1; + this.$.lon = object.$.lon === 0 || object.lon === 0 ? 0 : object.$.lon || object.lon || -1; + this.ele = object.ele; + this.time = object.time ? new Date(object.time) : new Date(); + this.magvar = object.magvar; + this.geoidheight = object.geoidheight; + this.name = object.name; + this.cmt = object.cmt; + this.desc = object.desc; + this.src = object.src; + this.sym = object.sym; + this.type = object.type; + this.sat = object.sat; + this.hdop = object.hdop; + this.vdop = object.vdop; + this.pdop = object.pdop; + this.ageofdgpsdata = object.ageofdgpsdata; + this.dgpsid = object.dgpsid; + this.extensions = object.extensions; + if (object.link) { + if (!Array.isArray(object.link)) { + object.link = [object.link]; + } + this.link = object.link.map(l => new Link(l)); + } + } +} \ No newline at end of file diff --git a/src/missing-typedefs/gpx-parser-builder.d.ts b/src/missing-typedefs/gpx-parser-builder.d.ts new file mode 100644 index 0000000..d50663c --- /dev/null +++ b/src/missing-typedefs/gpx-parser-builder.d.ts @@ -0,0 +1 @@ +declare module 'gpx-parser-builder'; \ No newline at end of file