├── .npmignore ├── test ├── fixtures │ ├── empty_trk.gpx.geojson │ ├── nogeomplacemark.kml.geojson │ ├── trek_no_rtept.gpx.geojson │ ├── unique_trkpt.gpx.geojson │ ├── 12-775-1538.png │ ├── 13-1548-3076.webp │ ├── 13-1549-3077.webp │ ├── 14-3099-6154.webp │ ├── 14-3099-6155.webp │ ├── 14-3101-6153.webp │ ├── 14-3102-6153.webp │ ├── 14-3103-6152.webp │ ├── trek_no_rtept.gpx │ ├── cdata.kml │ ├── empty_trk.gpx │ ├── selfclosing.kml │ ├── point.kml │ ├── point_id.kml │ ├── selfclosing.kml.geojson │ ├── cdata.kml.geojson │ ├── extended_data.kml │ ├── unique_trkpt.gpx │ ├── empty_ele.gpx │ ├── nogeomplacemark.kml │ ├── point.kml.geojson │ ├── point_id.kml.geojson │ ├── extended_data.kml.geojson │ ├── opacity_override.kml │ ├── multigeometry_discrete.kml │ ├── timespan.kml │ ├── twopoints.kml │ ├── opacity_override.kml.geojson │ ├── empty_ele.gpx.geojson │ ├── multigeometry.kml │ ├── noname.kml │ ├── literal_color.kml │ ├── gxtrack.kml │ ├── linestring.kml │ ├── style.kml │ ├── polygon.kml │ ├── timespan.kml.geojson │ ├── twopoints.kml.geojson │ ├── zero_elevation.gpx.geojson │ ├── style.kml.geojson │ ├── simpledata.kml.geojson │ ├── linestring.kml.geojson │ ├── multigeometry_discrete.kml.geojson │ ├── multigeometry.kml.geojson │ ├── style_url.kml │ ├── gdal.gpx │ ├── inline_style.kml │ ├── gxtrack.kml.geojson │ ├── noname.kml.geojson │ ├── zero_elevation.gpx │ ├── literal_color.kml.geojson │ ├── wpt.gpx │ ├── polygon.kml.geojson │ ├── simpledata.kml │ ├── utah-slopes.geojson │ ├── gdal.gpx.geojson │ ├── style_url.kml.geojson │ ├── multitrack.kml │ ├── non_gx_multitrack.kml │ ├── missing_hr.gpx │ ├── inline_style.kml.geojson │ ├── missing_hr.gpx.geojson │ ├── multitrack.kml.geojson │ ├── non_gx_multitrack.kml.geojson │ ├── gxmultitrack.kml │ ├── north_core.gpx.geojson │ └── addresses.kml ├── geometry │ ├── distance.test.ts │ └── util.test.ts └── demProfiler │ ├── getElevation.test.ts │ ├── tileCover.test.ts │ └── index.test.ts ├── images ├── maptiler-logo-256.png ├── maptiler-client-logo.afdesign ├── screenshots │ ├── static-with-path.png │ ├── static-bounded-europe-1024.png │ ├── static-bounded-europe-2048.png │ ├── static-bounded-portugal-1024x2048.png │ └── static-bounded-portugal-2048x2048.png ├── JS-logo.svg ├── TS-logo.svg └── maptiler-logo.svg ├── tsconfig.json ├── .github ├── pull_request_template.md └── workflows │ ├── format-lint.yml │ ├── npm-publish-dry-run.yml │ └── npm-publish.yml ├── src ├── defaults.ts ├── services │ ├── ServiceError.ts │ ├── data.ts │ ├── geolocation.ts │ ├── math.ts │ └── coordinates.ts ├── callFetch.ts ├── index.ts ├── config.ts ├── tiledecoding.ts └── misc.ts ├── .eslintrc.cjs ├── typedoc.json ├── LICENSE ├── examples ├── test-node.js ├── test-browser-umd.html ├── test-browser-es.html ├── elevation.html └── linestring.geojson ├── .gitignore ├── package.json ├── rollup.config.js └── CHANGELOG.md /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | docsmd 3 | images 4 | test 5 | src 6 | examples 7 | dist/*.umd.* -------------------------------------------------------------------------------- /test/fixtures/empty_trk.gpx.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [] 4 | } -------------------------------------------------------------------------------- /test/fixtures/nogeomplacemark.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [] 4 | } -------------------------------------------------------------------------------- /test/fixtures/trek_no_rtept.gpx.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [] 4 | } -------------------------------------------------------------------------------- /test/fixtures/unique_trkpt.gpx.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [] 4 | } -------------------------------------------------------------------------------- /images/maptiler-logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/images/maptiler-logo-256.png -------------------------------------------------------------------------------- /test/fixtures/12-775-1538.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/test/fixtures/12-775-1538.png -------------------------------------------------------------------------------- /test/fixtures/13-1548-3076.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/test/fixtures/13-1548-3076.webp -------------------------------------------------------------------------------- /test/fixtures/13-1549-3077.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/test/fixtures/13-1549-3077.webp -------------------------------------------------------------------------------- /test/fixtures/14-3099-6154.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/test/fixtures/14-3099-6154.webp -------------------------------------------------------------------------------- /test/fixtures/14-3099-6155.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/test/fixtures/14-3099-6155.webp -------------------------------------------------------------------------------- /test/fixtures/14-3101-6153.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/test/fixtures/14-3101-6153.webp -------------------------------------------------------------------------------- /test/fixtures/14-3102-6153.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/test/fixtures/14-3102-6153.webp -------------------------------------------------------------------------------- /test/fixtures/14-3103-6152.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/test/fixtures/14-3103-6152.webp -------------------------------------------------------------------------------- /images/maptiler-client-logo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/images/maptiler-client-logo.afdesign -------------------------------------------------------------------------------- /images/screenshots/static-with-path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/images/screenshots/static-with-path.png -------------------------------------------------------------------------------- /images/screenshots/static-bounded-europe-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/images/screenshots/static-bounded-europe-1024.png -------------------------------------------------------------------------------- /images/screenshots/static-bounded-europe-2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/images/screenshots/static-bounded-europe-2048.png -------------------------------------------------------------------------------- /images/screenshots/static-bounded-portugal-1024x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/images/screenshots/static-bounded-portugal-1024x2048.png -------------------------------------------------------------------------------- /images/screenshots/static-bounded-portugal-2048x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maptiler/maptiler-client-js/HEAD/images/screenshots/static-bounded-portugal-2048x2048.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "Bundler", 4 | "target": "es6", 5 | "declaration": true, 6 | "allowSyntheticDefaultImports": true, 7 | "resolveJsonModule": true 8 | } 9 | } -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Objective 2 | What is the goal? 3 | 4 | ## Description 5 | What changed, how and why? 6 | 7 | ## Acceptance 8 | How were changes tested? 9 | 10 | ## Checklist 11 | - [ ] I have added relevant info to the CHANGELOG.md -------------------------------------------------------------------------------- /src/defaults.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Some default settings for the SDK 3 | */ 4 | const defaults = { 5 | maptilerApiURL: "https://api.maptiler.com/", 6 | mapStyle: "streets-v2", 7 | }; 8 | 9 | Object.freeze(defaults); 10 | 11 | export { defaults }; 12 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint'], 5 | root: true, 6 | rules: { 7 | "@typescript-eslint/ban-ts-comment": "off", 8 | "@typescript-eslint/no-explicit-any": "warn", 9 | } 10 | }; -------------------------------------------------------------------------------- /test/fixtures/trek_no_rtept.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/cdata.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | -122.0822035425683,37.42228990140251,0 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/fixtures/empty_trk.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/services/ServiceError.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A ServiceError is an Error that includes the HTTP response details 3 | */ 4 | export class ServiceError extends Error { 5 | constructor( 6 | public res: Response, 7 | customMessage = "", 8 | ) { 9 | super( 10 | `Call to enpoint ${res.url} failed with the status code ${res.status}. ${customMessage}`, 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/selfclosing.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LineString.kml 5 | 1 6 | 7 | unextruded 8 | 9 | 1 10 | 1 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["./src/services/*.ts", "./src/language.ts", "src/config.ts"], 3 | "name": "MapTiler Client", 4 | "plugin": [], 5 | "out": "docs", 6 | "excludePrivate": true, 7 | "excludeProtected": true, 8 | "excludeExternals": true, 9 | "excludeNotDocumented": false, 10 | "excludeInternal": true, 11 | "theme": "default", 12 | "customCss": "typedoc.css", 13 | "includeVersion": true 14 | } -------------------------------------------------------------------------------- /test/fixtures/point.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple placemark 5 | Attached to the ground. Intelligently places itself 6 | at the height of the underlying terrain. 7 | 8 | -122.0822035425683,37.42228990140251,0 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/point_id.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple placemark 5 | Attached to the ground. Intelligently places itself 6 | at the height of the underlying terrain. 7 | 8 | -122.0822035425683,37.42228990140251,0 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/selfclosing.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "LineString", 8 | "coordinates": [ 9 | [ 10 | null 11 | ] 12 | ] 13 | }, 14 | "properties": { 15 | "name": "unextruded" 16 | } 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /test/fixtures/cdata.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | -122.0822035425683, 10 | 37.42228990140251, 11 | 0 12 | ] 13 | }, 14 | "properties": { 15 | "description": "\n \n\n Here is some text\n " 16 | } 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /test/fixtures/extended_data.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Extended data placemark 5 | Attached to the ground. Intelligently places itself 6 | at the height of the underlying terrain. 7 | 8 | 9 | bar 10 | 11 | 12 | 13 | -122.0822035425683,37.42228990140251,0 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/format-lint.yml: -------------------------------------------------------------------------------- 1 | name: Format and Lint 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | format-and-lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check out Git repository 10 | uses: actions/checkout@v3 11 | 12 | - name: Set up Node.js 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: 18 16 | 17 | - name: Install Node.js dependencies 18 | run: npm ci 19 | 20 | - name: Formatting with Prettier 21 | run: npm run format 22 | 23 | - name: Linting with ESLint 24 | run: npm run lint -------------------------------------------------------------------------------- /test/fixtures/unique_trkpt.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 559.5 7 | 8.0 8 | 9 | 10 | 0.5 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/callFetch.ts: -------------------------------------------------------------------------------- 1 | import { config } from "./config"; 2 | 3 | export async function callFetch(resource, options = {}) { 4 | if (config.fetch === null) { 5 | throw new Error( 6 | "The fetch function was not found. If on NodeJS < 18 please specify the fetch function with config.fetch", 7 | ); 8 | } 9 | 10 | // Control if URL contains the api key 11 | if (new URL(resource).searchParams.get("key").trim() === "") { 12 | throw new Error( 13 | "The MapTiler Cloud API key is missing. Set it in `config.apiKey` or get one for free at https://maptiler.com", 14 | ); 15 | } 16 | 17 | return config.fetch(resource, options); 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/empty_ele.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/fixtures/nogeomplacemark.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inline style test 6 | 7 | With all inline styles 8 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/point.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | -122.0822035425683, 10 | 37.42228990140251, 11 | 0 12 | ] 13 | }, 14 | "properties": { 15 | "name": "Simple placemark", 16 | "description": "Attached to the ground. Intelligently places itself \n at the height of the underlying terrain." 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /test/fixtures/point_id.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | -122.0822035425683, 10 | 37.42228990140251, 11 | 0 12 | ] 13 | }, 14 | "properties": { 15 | "name": "Simple placemark", 16 | "description": "Attached to the ground. Intelligently places itself \n at the height of the underlying terrain." 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { BBox, Position, LineString, MultiLineString } from "geojson"; 2 | export type { BBox, Position, LineString, MultiLineString }; 3 | export * from "./config"; 4 | export * from "./language"; 5 | export * from "./services/geocoding"; 6 | export * from "./services/geolocation"; 7 | export * from "./services/coordinates"; 8 | export * from "./services/data"; 9 | export * from "./services/staticMaps"; 10 | export * from "./services/ServiceError"; 11 | export * from "./mapstyle"; 12 | export * from "./services/math"; 13 | export * from "./services/elevation"; 14 | export * from "./tiledecoding"; 15 | export * from "./misc"; 16 | export * from "./language"; 17 | -------------------------------------------------------------------------------- /images/JS-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/extended_data.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | -122.0822035425683, 10 | 37.42228990140251, 11 | 0 12 | ] 13 | }, 14 | "properties": { 15 | "name": "Extended data placemark", 16 | "description": "Attached to the ground. Intelligently places itself \n at the height of the underlying terrain.", 17 | "foo": "bar" 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /test/fixtures/opacity_override.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inline style test 6 | 7 | 18 | 19 | 2.3101624,48.7301875 2.3098714,48.7300247 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/fixtures/multigeometry_discrete.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Polygon.kml 5 | 0 6 | 7 | SF Marina Harbor Master 8 | 0 9 | 10 | 11 | 12 | 13 | -122.4425587930444,37.80666418607323,0 14 | -122.4428379594768,37.80663578323093,0 15 | 16 | 17 | 18 | 19 | -122.4428340530617,37.8065999493009,0 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/timespan.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Features 6 | 0 7 | 8 | 1 9 | 07250733#FEATURES 10 | 11 | 12 | 13 | 14 | 115.87726,24.93583,0 15 | 115.87075,24.93053,0 16 | 115.87439,24.90768,0 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/fixtures/twopoints.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple placemark 5 | Attached to the ground. Intelligently places itself 6 | at the height of the underlying terrain. 7 | 8 | -122.0822035425683,37.42228990140251,0 9 | 10 | 11 | 12 | Simple placemark two 13 | Attached to the ground. Intelligently places itself 14 | at the height of the underlying terrain. 15 | 16 | -120.0822035425683,37.42228990140251,0 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/opacity_override.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "LineString", 8 | "coordinates": [ 9 | [ 10 | 2.3101624, 11 | 48.7301875 12 | ], 13 | [ 14 | 2.3098714, 15 | 48.7300247 16 | ] 17 | ] 18 | }, 19 | "properties": { 20 | "stroke-width": 3, 21 | "fill": "#1100ff", 22 | "fill-opacity": 0.8, 23 | "stroke-opacity": 0 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /test/fixtures/empty_ele.gpx.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "time": "2007-05-11T15:22:06Z", 8 | "coordTimes": [ 9 | "2007-05-11T15:22:06Z", 10 | "2007-05-11T15:22:06Z" 11 | ] 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [ 16 | [ 17 | -77.01059, 18 | 38.892101 19 | ], 20 | [ 21 | -77.01059, 22 | 38.892101 23 | ] 24 | ] 25 | } 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /test/fixtures/multigeometry.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Polygon.kml 5 | 0 6 | 7 | SF Marina Harbor Master 8 | 0 9 | 10 | 11 | 12 | 13 | -122.4425587930444,37.80666418607323,0 14 | -122.4428379594768,37.80663578323093,0 15 | 16 | 17 | 18 | 19 | 20 | -122.4425509770566,37.80662588061205,0 21 | -122.4428340530617,37.8065999493009,0 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/fixtures/noname.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inline style test 6 | 7 | 18 | 19 | 2.3101624,48.7301875 2.3098714,48.7300247 2.3098051,48.7299542 2.3098493,48.7298813 2.309934,48.7298108 2.3100372,48.729806 2.3101293,48.7298424 2.3101772,48.7298862 2.3101661,48.7299542 2.3101587,48.7300538 2.3101624,48.7301875 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/fixtures/literal_color.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inline style test 6 | 7 | With all inline styles 8 | 19 | 20 | 2.3101624,48.7301875 2.3098714,48.7300247 2.3098051,48.7299542 2.3098493,48.7298813 2.309934,48.7298108 2.3100372,48.729806 2.3101293,48.7298424 2.3101772,48.7298862 2.3101661,48.7299542 2.3101587,48.7300538 2.3101624,48.7301875 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/gxtrack.kml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 2010-05-28T02:02:09Z 8 | 2010-05-28T02:02:35Z 9 | 2010-05-28T02:02:44Z 10 | 2010-05-28T02:02:53Z 11 | 2010-05-28T02:02:54Z 12 | 2010-05-28T02:02:55Z 13 | 2010-05-28T02:02:56Z 14 | -122.207881 37.371915 156.000000 15 | -122.205712 37.373288 152.000000 16 | -122.204678 37.373939 147.000000 17 | -122.203572 37.374630 142.199997 18 | -122.203451 37.374706 141.800003 19 | -122.203329 37.374780 141.199997 20 | -122.203207 37.374857 140.199997 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/fixtures/linestring.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LineString.kml 5 | 1 6 | 7 | -122.36415 8 | 37.824553 9 | 0 10 | 150 11 | 50 12 | 0 13 | 14 | 15 | unextruded 16 | 17 | 1 18 | 1 19 | 20 | -122.364383,37.824664,0 -122.364152,37.824322,0 21 | 22 | 23 | 24 | 25 | extruded 26 | 27 | 1 28 | 1 29 | relativeToGround 30 | 31 | -122.364167,37.824787,50 -122.363917,37.824423,50 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/fixtures/style.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 21 | 22 | Google Earth - New Polygon 23 | Here is some descriptive text 24 | #a 25 | 26 | -122.0822035425683,37.42228990140251,0 27 | 28 | 29 | 30 | Google Earth - New Path 31 | #b 32 | 33 | -122.0822035425683,37.42228990140251,0 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/fixtures/polygon.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Polygon.kml 5 | 0 6 | 7 | hollow box 8 | 9 | 1 10 | relativeToGround 11 | 12 | 13 | 14 | -122.366278,37.818844,30 15 | -122.365248,37.819267,30 16 | -122.365640,37.819861,30 17 | -122.366669,37.819429,30 18 | -122.366278,37.818844,30 19 | 20 | 21 | 22 | 23 | 24 | 25 | -122.366212,37.818977,30 26 | -122.365424,37.819294,30 27 | -122.365704,37.819731,30 28 | -122.366488,37.819402,30 29 | -122.366212,37.818977,30 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/fixtures/timespan.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Polygon", 8 | "coordinates": [ 9 | [ 10 | [ 11 | 115.87726, 12 | 24.93583, 13 | 0 14 | ], 15 | [ 16 | 115.87075, 17 | 24.93053, 18 | 0 19 | ], 20 | [ 21 | 115.87439, 22 | 24.90768, 23 | 0 24 | ] 25 | ] 26 | ] 27 | }, 28 | "properties": { 29 | "name": "1", 30 | "styleUrl": "#FEATURES", 31 | "timespan": { 32 | "begin": "0725", 33 | "end": "0733" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /test/fixtures/twopoints.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | -122.0822035425683, 10 | 37.42228990140251, 11 | 0 12 | ] 13 | }, 14 | "properties": { 15 | "name": "Simple placemark", 16 | "description": "Attached to the ground. Intelligently places itself \n at the height of the underlying terrain." 17 | } 18 | }, 19 | { 20 | "type": "Feature", 21 | "geometry": { 22 | "type": "Point", 23 | "coordinates": [ 24 | -120.0822035425683, 25 | 37.42228990140251, 26 | 0 27 | ] 28 | }, 29 | "properties": { 30 | "name": "Simple placemark two", 31 | "description": "Attached to the ground. Intelligently places itself \n at the height of the underlying terrain." 32 | } 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /test/fixtures/zero_elevation.gpx.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "name": "BIG LOOP", 8 | "cmt": "Sun Jun 24 15:08:39 2001", 9 | "desc": "Big loop around the base of Great Blue Hill", 10 | "time": "2001-06-24T15:09:09Z", 11 | "coordTimes": [ 12 | "2001-06-24T15:09:09Z", 13 | "2001-06-24T15:09:29Z", 14 | "2001-06-24T15:16:04Z" 15 | ] 16 | }, 17 | "geometry": { 18 | "type": "LineString", 19 | "coordinates": [ 20 | [ 21 | -71.09622, 22 | 42.210009, 23 | 61.5696 24 | ], 25 | [ 26 | -71.09622, 27 | 42.210031, 28 | 0 29 | ], 30 | [ 31 | -71.096284, 32 | 42.210052, 33 | 62.484 34 | ] 35 | ] 36 | } 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /images/TS-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | TypeScript logo 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/style.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | -122.0822035425683, 10 | 37.42228990140251, 11 | 0 12 | ] 13 | }, 14 | "properties": { 15 | "icon": "http://myserver.com/icon.jpg", 16 | "name": "Google Earth - New Polygon", 17 | "styleUrl": "#a", 18 | "styleHash": "-1d694122", 19 | "description": "Here is some descriptive text" 20 | } 21 | }, 22 | { 23 | "type": "Feature", 24 | "geometry": { 25 | "type": "Point", 26 | "coordinates": [ 27 | -122.0822035425683, 28 | 37.42228990140251, 29 | 0 30 | ] 31 | }, 32 | "properties": { 33 | "icon": "http://myserver.com/icon.jpg", 34 | "name": "Google Earth - New Path", 35 | "styleUrl": "#b", 36 | "styleHash": "-5b6ad921" 37 | } 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /test/fixtures/simpledata.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | -122, 10 | 37.002 11 | ] 12 | }, 13 | "properties": { 14 | "name": "Easy trail", 15 | "styleUrl": "#trailhead-balloon-template", 16 | "styleHash": "721d7249", 17 | "TrailHeadName": "Pi in the sky", 18 | "TrailLength": "3.14159", 19 | "ElevationGain": "10" 20 | } 21 | }, 22 | { 23 | "type": "Feature", 24 | "geometry": { 25 | "type": "Point", 26 | "coordinates": [ 27 | -121.998, 28 | 37.0078 29 | ] 30 | }, 31 | "properties": { 32 | "name": "Difficult trail", 33 | "styleUrl": "#trailhead-balloon-template", 34 | "styleHash": "721d7249", 35 | "TrailHeadName": "Mount Everest", 36 | "TrailLength": "347.45", 37 | "ElevationGain": "10000" 38 | } 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /test/fixtures/linestring.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "LineString", 8 | "coordinates": [ 9 | [ 10 | -122.364383, 11 | 37.824664, 12 | 0 13 | ], 14 | [ 15 | -122.364152, 16 | 37.824322, 17 | 0 18 | ] 19 | ] 20 | }, 21 | "properties": { 22 | "name": "unextruded" 23 | } 24 | }, 25 | { 26 | "type": "Feature", 27 | "geometry": { 28 | "type": "LineString", 29 | "coordinates": [ 30 | [ 31 | -122.364167, 32 | 37.824787, 33 | 50 34 | ], 35 | [ 36 | -122.363917, 37 | 37.824423, 38 | 50 39 | ] 40 | ] 41 | }, 42 | "properties": { 43 | "name": "extruded" 44 | } 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /test/fixtures/multigeometry_discrete.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "GeometryCollection", 8 | "geometries": [ 9 | { 10 | "type": "LineString", 11 | "coordinates": [ 12 | [ 13 | -122.4425587930444, 14 | 37.80666418607323, 15 | 0 16 | ], 17 | [ 18 | -122.4428379594768, 19 | 37.80663578323093, 20 | 0 21 | ] 22 | ] 23 | }, 24 | { 25 | "type": "Point", 26 | "coordinates": [ 27 | -122.4428340530617, 28 | 37.8065999493009, 29 | 0 30 | ] 31 | } 32 | ] 33 | }, 34 | "properties": { 35 | "name": "SF Marina Harbor Master", 36 | "visibility": "0" 37 | } 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, MapTiler 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /.github/workflows/npm-publish-dry-run.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | pull_request: 8 | types: [opened] 9 | 10 | jobs: 11 | build-and-publish: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | with: 17 | ref: ${{ github.event.release.target_commitish }} 18 | 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: 18 22 | registry-url: https://registry.npmjs.org/ 23 | 24 | - name: Clear NPM cache 25 | run: npm cache clean --force 26 | 27 | - name: Install dependencies and build 28 | run: npm ci 29 | 30 | - name: Make release 31 | run: npm run build 32 | id: makeRelease 33 | 34 | - name: Fail job if makeRelease failed 35 | if: steps.makeRelease.outcome == 'failure' 36 | run: exit 1 37 | 38 | id: check-build-status 39 | - name: Publish NPM package (regular) 40 | if: "!github.event.release.prerelease" 41 | run: | 42 | npm publish --dry-run 43 | env: 44 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 45 | 46 | - name: Publish NPM package (pre-release) 47 | if: "github.event.release.prerelease" 48 | run: | 49 | npm publish --tag next --dry-run 50 | env: 51 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 52 | -------------------------------------------------------------------------------- /test/fixtures/multigeometry.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "GeometryCollection", 8 | "geometries": [ 9 | { 10 | "type": "LineString", 11 | "coordinates": [ 12 | [ 13 | -122.4425587930444, 14 | 37.80666418607323, 15 | 0 16 | ], 17 | [ 18 | -122.4428379594768, 19 | 37.80663578323093, 20 | 0 21 | ] 22 | ] 23 | }, 24 | { 25 | "type": "LineString", 26 | "coordinates": [ 27 | [ 28 | -122.4425509770566, 29 | 37.80662588061205, 30 | 0 31 | ], 32 | [ 33 | -122.4428340530617, 34 | 37.8065999493009, 35 | 0 36 | ] 37 | ] 38 | } 39 | ] 40 | }, 41 | "properties": { 42 | "name": "SF Marina Harbor Master", 43 | "visibility": "0" 44 | } 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { BufferToPixelDataFunction } from "./tiledecoding"; 2 | 3 | export type FetchFunction = ( 4 | input: RequestInfo | URL, 5 | init?: RequestInit, 6 | ) => Promise; 7 | 8 | function tryGettingFetch() { 9 | // this is browser, fetch exists 10 | if (typeof self !== "undefined") { 11 | return fetch.bind(self); 12 | } 13 | 14 | if (typeof global !== "undefined" && global.fetch) { 15 | return global.fetch; 16 | } 17 | 18 | return null; 19 | } 20 | 21 | /** 22 | * The configuration object definition 23 | */ 24 | class ClientConfig { 25 | /** 26 | * MapTiler Cloud API key 27 | */ 28 | private _apiKey = ""; 29 | 30 | /** 31 | * The fetch function. To be set if in Node < 18, otherwise 32 | * will be automatically resolved. 33 | */ 34 | private _fetch: FetchFunction | null = tryGettingFetch(); 35 | 36 | /** 37 | * Number of tiles to keep in cache 38 | */ 39 | public tileCacheSize: number = 200; 40 | 41 | public bufferToPixelData?: BufferToPixelDataFunction | null; 42 | 43 | /** 44 | * Set the MapTiler Cloud API key 45 | */ 46 | set apiKey(k: string) { 47 | this._apiKey = k; 48 | } 49 | 50 | /** 51 | * Get the MapTiler Cloud API key 52 | */ 53 | get apiKey(): string { 54 | return this._apiKey; 55 | } 56 | 57 | /** 58 | * Set a the custom fetch function to replace the default one 59 | */ 60 | set fetch(f: FetchFunction) { 61 | this._fetch = f; 62 | } 63 | 64 | /** 65 | * Get the fetch fucntion 66 | */ 67 | get fetch(): FetchFunction | null { 68 | return this._fetch; 69 | } 70 | } 71 | 72 | /** 73 | * Configuration object 74 | */ 75 | const config = new ClientConfig(); 76 | 77 | export { ClientConfig, config }; 78 | -------------------------------------------------------------------------------- /examples/test-node.js: -------------------------------------------------------------------------------- 1 | import { 2 | config, 3 | geocoding, 4 | geolocation, 5 | coordinates, 6 | data, 7 | Language, 8 | } from '../dist/maptiler-client.mjs'; 9 | 10 | // For this examople to work, you must bring your own node-compatible fetch, 11 | // unles you are using a version of Nodejs that already contains fetch (>=18) 12 | // import fetch from 'node-fetch'; 13 | 14 | // config.fetch = fetch; 15 | config.apiKey = 'YOUR_API_KEY'; 16 | 17 | async function testGeocoding() { 18 | const result1 = await geocoding.forward('bordeaux', {language: [Language.AUTO, Language.ENGLISH]}); 19 | console.log(result1); 20 | 21 | // const result2 = await geocoding.reverse([6.249638, 46.402056], {language: ['es', 'en']}); 22 | // console.log(result2); 23 | } 24 | 25 | async function testGeolocation() { 26 | const result = await geolocation.info(); 27 | console.log(result); 28 | } 29 | 30 | async function testCoordinates() { 31 | // searching 32 | console.log(await coordinates.search('mercator', {transformations: true})); 33 | console.log(await coordinates.search('plate carree')); 34 | console.log(await coordinates.search('france')); 35 | console.log(await coordinates.search('4326', {transformations: true})); 36 | console.log(await coordinates.search('4326')); 37 | 38 | // Transforming from wgs84 (default) to lambert93 39 | console.log(await coordinates.transform({lng: 1, lat: 45}, {targetCrs: 9793})); 40 | console.log(await coordinates.transform([{lng: 10, lat: 48},{lng: 1, lat: 45}], {targetCrs: 9793})); 41 | } 42 | 43 | async function testData() { 44 | console.log(await data.get('2dd5ecc4-3ae1-4d1e-99a2-182256486357')); 45 | } 46 | 47 | (async () => { 48 | await testGeocoding(); 49 | // await testGeolocation(); 50 | // await testCoordinates(); 51 | // await testData(); 52 | })() -------------------------------------------------------------------------------- /test/fixtures/style_url.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | Building 41 14 | #transBluePoly 15 | 16 | 1 17 | relativeToGround 18 | 19 | 20 | -122.0857412771483,37.42227033155257,17 21 | -122.0858169768481,37.42231408832346,17 22 | -122.085852582875,37.42230337469744,17 23 | -122.0858799945639,37.42225686138789,17 24 | -122.0858860101409,37.4222311076138,17 25 | -122.0858069157288,37.42220250173855,17 26 | -122.0858379542653,37.42214027058678,17 27 | -122.0856732640519,37.42208690214408,17 28 | -122.0856022926407,37.42214885429042,17 29 | -122.0855902778436,37.422128290487,17 30 | -122.0855841672237,37.42208171967246,17 31 | -122.0854852065741,37.42210455874995,17 32 | -122.0855067264352,37.42214267949824,17 33 | -122.0854430712915,37.42212783846172,17 34 | -122.0850990714904,37.42251282407603,17 35 | -122.0856769818632,37.42281815323651,17 36 | -122.0860162273783,37.42244918858722,17 37 | -122.0857260327004,37.42229239604253,17 38 | -122.0857412771483,37.42227033155257,17 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/fixtures/gdal.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2 6 | waypoint name 7 | waypoint comment 8 | waypoint description 9 | waypoint source 10 | texttype 11 | text2type2 12 | text3type3 13 | 14 | 15 | 16 | 17 | 18 | route name 19 | 20 | 7 21 | route point name 22 | 23 | 24 | 25 | 10 26 | 27 | 28 | 13 29 | 30 | 31 | 32 | empty route 33 | 34 | 35 | track name 36 | 37 | 38 | 16 39 | track point name 40 | 41 | 42 | 19 43 | 44 | 45 | 46 | 47 | 22 48 | 49 | 50 | 25 51 | 52 | 53 | 54 | 55 | empty track 56 | 57 | 58 | empty track 2 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/services/data.ts: -------------------------------------------------------------------------------- 1 | import { FeatureCollection } from "geojson"; 2 | import { callFetch } from "../callFetch"; 3 | import { config } from "../config"; 4 | import { defaults } from "../defaults"; 5 | import { ServiceError } from "./ServiceError"; 6 | 7 | const customMessages = { 8 | 403: "Key is missing, invalid or restricted", 9 | }; 10 | 11 | /** 12 | * Options that can be provided to get user data. 13 | */ 14 | export type GetDataOptions = { 15 | /** 16 | * Custom MapTiler Cloud API key to use instead of the one in global `config` 17 | */ 18 | apiKey?: string; 19 | }; 20 | 21 | /** 22 | * Get user data and returns it as GeoJSON using the MapTiler API. 23 | * Learn more on the MapTiler API reference page: https://docs.maptiler.com/cloud/api/data/#geojson 24 | * @param dataId 25 | * @returns 26 | */ 27 | async function get( 28 | dataId: string, 29 | options: GetDataOptions = {}, 30 | ): Promise { 31 | if (typeof dataId !== "string" || dataId.trim().length === 0) { 32 | throw new Error("The data ID must be a non-empty string"); 33 | } 34 | 35 | const endpoint = new URL( 36 | `data/${encodeURIComponent(dataId)}/features.json`, 37 | defaults.maptilerApiURL, 38 | ); 39 | endpoint.searchParams.set("key", options.apiKey ?? config.apiKey); 40 | const urlWithParams = endpoint.toString(); 41 | 42 | const res = await callFetch(urlWithParams); 43 | 44 | if (!res.ok) { 45 | throw new ServiceError( 46 | res, 47 | res.status in customMessages ? customMessages[res.status] : "", 48 | ); 49 | } 50 | 51 | const obj = await res.json(); 52 | return obj; 53 | } 54 | 55 | /** 56 | * The **data** namespace contains an asynchronous function to call the [MapTiler Data API](https://docs.maptiler.com/cloud/api/data/). 57 | * The **Data API** provides a way to retrieve user data in GeoJSON format. 58 | */ 59 | const data = { 60 | get, 61 | }; 62 | 63 | export { data }; 64 | -------------------------------------------------------------------------------- /test/fixtures/inline_style.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inline style test 6 | 7 | With all inline styles 8 | 19 | 20 | 2.3101624,48.7301875 2.3098714,48.7300247 2.3098051,48.7299542 2.3098493,48.7298813 2.309934,48.7298108 2.3100372,48.729806 2.3101293,48.7298424 2.3101772,48.7298862 2.3101661,48.7299542 2.3101587,48.7300538 2.3101624,48.7301875 21 | 22 | 23 | 24 | With some inline styles 25 | 33 | 34 | 2.3101624,48.7301875 2.3098714,48.7300247 2.3098051,48.7299542 2.3098493,48.7298813 2.309934,48.7298108 2.3100372,48.729806 2.3101293,48.7298424 2.3101772,48.7298862 2.3101661,48.7299542 2.3101587,48.7300538 2.3101624,48.7301875 35 | 36 | 37 | 38 | Without inline style 39 | 40 | 2.3131898,48.7315075 2.3123173,48.7309455 2.312019,48.7308143 2.3114185,48.730671 2.310796,48.7305252 2.3104424,48.7303916 2.3101624,48.7301875 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/fixtures/gxtrack.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "LineString", 8 | "coordinates": [ 9 | [ 10 | -122.207881, 11 | 37.371915, 12 | 156 13 | ], 14 | [ 15 | -122.205712, 16 | 37.373288, 17 | 152 18 | ], 19 | [ 20 | -122.204678, 21 | 37.373939, 22 | 147 23 | ], 24 | [ 25 | -122.203572, 26 | 37.37463, 27 | 142.199997 28 | ], 29 | [ 30 | -122.203451, 31 | 37.374706, 32 | 141.800003 33 | ], 34 | [ 35 | -122.203329, 36 | 37.37478, 37 | 141.199997 38 | ], 39 | [ 40 | -122.203207, 41 | 37.374857, 42 | 140.199997 43 | ] 44 | ] 45 | }, 46 | "properties": { 47 | "coordTimes": [ 48 | "2010-05-28T02:02:09Z", 49 | "2010-05-28T02:02:35Z", 50 | "2010-05-28T02:02:44Z", 51 | "2010-05-28T02:02:53Z", 52 | "2010-05-28T02:02:54Z", 53 | "2010-05-28T02:02:55Z", 54 | "2010-05-28T02:02:56Z" 55 | ] 56 | } 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /test/fixtures/noname.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "LineString", 8 | "coordinates": [ 9 | [ 10 | 2.3101624, 11 | 48.7301875 12 | ], 13 | [ 14 | 2.3098714, 15 | 48.7300247 16 | ], 17 | [ 18 | 2.3098051, 19 | 48.7299542 20 | ], 21 | [ 22 | 2.3098493, 23 | 48.7298813 24 | ], 25 | [ 26 | 2.309934, 27 | 48.7298108 28 | ], 29 | [ 30 | 2.3100372, 31 | 48.729806 32 | ], 33 | [ 34 | 2.3101293, 35 | 48.7298424 36 | ], 37 | [ 38 | 2.3101772, 39 | 48.7298862 40 | ], 41 | [ 42 | 2.3101661, 43 | 48.7299542 44 | ], 45 | [ 46 | 2.3101587, 47 | 48.7300538 48 | ], 49 | [ 50 | 2.3101624, 51 | 48.7301875 52 | ] 53 | ] 54 | }, 55 | "properties": { 56 | "stroke": "ff0", 57 | "stroke-width": 3, 58 | "fill": "ff0011", 59 | "fill-opacity": 1, 60 | "stroke-opacity": 0 61 | } 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /test/fixtures/zero_elevation.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | trails@topografix.com 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 3 23 | 00ffff 24 | 25 | 26 | 61.569600 27 | 28 | 29 | Dot 30 | 31 | 32 | 0 33 | 34 | 35 | Dot 36 | 37 | 38 | 62.484000 39 | 40 | 41 | Dot 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/fixtures/literal_color.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "LineString", 8 | "coordinates": [ 9 | [ 10 | 2.3101624, 11 | 48.7301875 12 | ], 13 | [ 14 | 2.3098714, 15 | 48.7300247 16 | ], 17 | [ 18 | 2.3098051, 19 | 48.7299542 20 | ], 21 | [ 22 | 2.3098493, 23 | 48.7298813 24 | ], 25 | [ 26 | 2.309934, 27 | 48.7298108 28 | ], 29 | [ 30 | 2.3100372, 31 | 48.729806 32 | ], 33 | [ 34 | 2.3101293, 35 | 48.7298424 36 | ], 37 | [ 38 | 2.3101772, 39 | 48.7298862 40 | ], 41 | [ 42 | 2.3101661, 43 | 48.7299542 44 | ], 45 | [ 46 | 2.3101587, 47 | 48.7300538 48 | ], 49 | [ 50 | 2.3101624, 51 | 48.7301875 52 | ] 53 | ] 54 | }, 55 | "properties": { 56 | "name": "With all inline styles", 57 | "stroke": "ff0", 58 | "stroke-width": 3, 59 | "fill": "ff0011", 60 | "fill-opacity": 1, 61 | "stroke-opacity": 0 62 | } 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /test/fixtures/wpt.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build-and-publish: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | with: 17 | ref: ${{ github.event.release.target_commitish }} 18 | 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: 18 22 | registry-url: https://registry.npmjs.org/ 23 | 24 | - name: Clear NPM cache 25 | run: npm cache clean --force 26 | 27 | - name: Install dependencies and build 28 | run: npm ci 29 | 30 | - name: Make release 31 | run: npm run build 32 | id: makeRelease 33 | 34 | - name: Fail job if makeRelease failed 35 | if: steps.makeRelease.outcome == 'failure' 36 | run: exit 1 37 | 38 | id: check-build-status 39 | - name: Publish NPM package (regular) 40 | if: "!github.event.release.prerelease" 41 | run: | 42 | npm publish 43 | env: 44 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 45 | 46 | - name: Publish NPM package (pre-release) 47 | if: "github.event.release.prerelease" 48 | run: | 49 | npm publish --tag next 50 | env: 51 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 52 | - name: Get Package version 53 | id: version 54 | run: echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV 55 | - name: Publish to CDN Cloudflar R2 56 | uses: ryand56/r2-upload-action@v1.4 57 | with: 58 | r2-account-id: ${{ secrets.CDN_MAPTILER_ACCOUNT_ID }} 59 | r2-access-key-id: ${{ secrets.CDN_MAPTILER_ACCESS_KEY_ID }} 60 | r2-secret-access-key: ${{ secrets.CDN_MAPTILER_SECRET_ACCESS_KEY }} 61 | r2-bucket: cdn-storage 62 | source-dir: dist 63 | destination-dir: client-js/v${{ env.PACKAGE_VERSION }}/ 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | .DS_Store 107 | 108 | dist 109 | dist/* 110 | docs 111 | docs/* 112 | docsmd 113 | docsmd/* -------------------------------------------------------------------------------- /test/geometry/distance.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest"; 2 | import { pointDistance, lineDistance } from "../../src/geometry"; 3 | 4 | // test distance 5 | describe("pointDistance", () => { 6 | it("calculates pointDistance between two points", () => { 7 | expect(pointDistance([0, 0], [0, 0])).toEqual(0); 8 | expect(pointDistance([0, 0], [0, 1])).toBeCloseTo(111195.0802335329); 9 | expect(pointDistance([0, 0], [1, 0])).toBeCloseTo(111195.0802335329); 10 | expect(pointDistance([0, 0], [1, 1])).toBeCloseTo(157249.5984740402); 11 | expect(pointDistance([0, 0], [0, 85])).toBeCloseTo(9451581.81985029); 12 | expect(pointDistance([0, 0], [85, 0])).toBeCloseTo(9451581.81985029); 13 | expect(pointDistance([0, 0], [80, 80])).toBeCloseTo(9815418.67483913); 14 | }); 15 | }); 16 | 17 | describe("lineDistance", () => { 18 | it("calculates lineDistance between two points", () => { 19 | expect( 20 | lineDistance([ 21 | [0, 0], 22 | [0, 0], 23 | ]), 24 | ).toEqual(0); 25 | expect( 26 | lineDistance([ 27 | [0, 0], 28 | [0, 1], 29 | ]), 30 | ).toBeCloseTo(111195.0802335329); 31 | expect( 32 | lineDistance([ 33 | [0, 0], 34 | [1, 0], 35 | ]), 36 | ).toBeCloseTo(111195.0802335329); 37 | expect( 38 | lineDistance([ 39 | [0, 0], 40 | [1, 1], 41 | ]), 42 | ).toBeCloseTo(157249.5984740402); 43 | expect( 44 | lineDistance([ 45 | [0, 0], 46 | [0, 85], 47 | ]), 48 | ).toBeCloseTo(9451581.81985029); 49 | expect( 50 | lineDistance([ 51 | [0, 0], 52 | [85, 0], 53 | ]), 54 | ).toBeCloseTo(9451581.81985029); 55 | expect( 56 | lineDistance([ 57 | [0, 0], 58 | [80, 80], 59 | ]), 60 | ).toBeCloseTo(9815418.67483913); 61 | }); 62 | 63 | it("calculates lineDistance between ten points", () => { 64 | expect( 65 | lineDistance([ 66 | [0, 0], 67 | [10, 10], 68 | [20, 20], 69 | [30, 30], 70 | [40, 40], 71 | [50, 50], 72 | [60, 60], 73 | [70, 70], 74 | [80, 80], 75 | [90, 90], 76 | ]), 77 | ).toBeCloseTo(12145778.91731941); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/fixtures/polygon.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Polygon", 8 | "coordinates": [ 9 | [ 10 | [ 11 | -122.366278, 12 | 37.818844, 13 | 30 14 | ], 15 | [ 16 | -122.365248, 17 | 37.819267, 18 | 30 19 | ], 20 | [ 21 | -122.36564, 22 | 37.819861, 23 | 30 24 | ], 25 | [ 26 | -122.366669, 27 | 37.819429, 28 | 30 29 | ], 30 | [ 31 | -122.366278, 32 | 37.818844, 33 | 30 34 | ] 35 | ], 36 | [ 37 | [ 38 | -122.366212, 39 | 37.818977, 40 | 30 41 | ], 42 | [ 43 | -122.365424, 44 | 37.819294, 45 | 30 46 | ], 47 | [ 48 | -122.365704, 49 | 37.819731, 50 | 30 51 | ], 52 | [ 53 | -122.366488, 54 | 37.819402, 55 | 30 56 | ], 57 | [ 58 | -122.366212, 59 | 37.818977, 60 | 30 61 | ] 62 | ] 63 | ] 64 | }, 65 | "properties": { 66 | "name": "hollow box" 67 | } 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /test/demProfiler/getElevation.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest"; 2 | import getElevation from "../../src/demProfiler/getElevation"; 3 | import sharp from "sharp"; 4 | 5 | const pngTile = async () => await sharp("test/fixtures/12-775-1538.png").raw().toBuffer(); 6 | const webpTile = async () => await sharp("test/fixtures/13-1548-3076.webp") 7 | .raw() 8 | .toBuffer(); 9 | 10 | describe("getElevation", () => { 11 | it("png: returns the elevation of a point", async () => { 12 | const coord: [lon: number, lat: number] = [ 13 | -111.8408203125, 40.81380923056976, 14 | ]; 15 | const tile: [zoom: number, x: number, y: number] = [12, 775, 1538]; 16 | const tileSize = 512; 17 | // convert to a Uint8Array 18 | const tileImage = { 19 | channels: 3 as const, 20 | image: new Uint8ClampedArray(await pngTile()), 21 | }; 22 | const elevation = getElevation(coord, tile, tileSize, tileImage); 23 | expect(elevation).toBeCloseTo(1657.4); 24 | }); 25 | 26 | // repeat the last test but with a custom elevation parser 27 | it("png: returns the elevation of a point with a custom elevation parser", async () => { 28 | const coord: [lon: number, lat: number] = [ 29 | -111.8408203125, 40.81380923056976, 30 | ]; 31 | const tile: [zoom: number, x: number, y: number] = [12, 775, 1538]; 32 | const tileSize = 512; 33 | // convert to a Uint8Array 34 | const tileImage = { 35 | channels: 3 as const, 36 | image: new Uint8ClampedArray(await pngTile()), 37 | }; 38 | const elevation = getElevation( 39 | coord, 40 | tile, 41 | tileSize, 42 | tileImage, 43 | (r, g, b, a) => { 44 | return r + g + b + a; 45 | }, 46 | ); 47 | expect(elevation).toEqual(294); 48 | }); 49 | 50 | it("webp: returns the elevation of a point", async () => { 51 | const coord: [lon: number, lat: number] = [ 52 | -111.95068359375, 40.8304347933204, 53 | ]; 54 | const tile: [zoom: number, x: number, y: number] = [13, 1548, 3076]; 55 | const tileSize = 512; 56 | // convert to a Uint8Array 57 | const tileImage = { 58 | channels: 3 as const, 59 | image: new Uint8ClampedArray(await webpTile()), 60 | }; 61 | const elevation = getElevation(coord, tile, tileSize, tileImage); 62 | expect(elevation).toBeCloseTo(1287.7); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/fixtures/simpledata.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ExtendedData+SchemaData 4 | 1 5 | 6 | 19 | 20 | 21 | 22 | 23 | Trail Head Name]]> 24 | 25 | 26 | The length in miles]]> 27 | 28 | 29 | change in altitude]]> 30 | 31 | 32 | 33 | 34 | 35 | Easy trail 36 | #trailhead-balloon-template 37 | 38 | 39 | Pi in the sky 40 | 3.14159 41 | 10 42 | 43 | 44 | 45 | -122.000,37.002 46 | 47 | 48 | 49 | Difficult trail 50 | #trailhead-balloon-template 51 | 52 | 53 | Mount Everest 54 | 347.45 55 | 10000 56 | 57 | 58 | 59 | -121.998,37.0078 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/fixtures/utah-slopes.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "coordinates": [ 9 | [ 10 | -111.6560681499893, 11 | 40.58097771542285 12 | ], 13 | [ 14 | -111.65559670065572, 15 | 40.57668085991753 16 | ], 17 | [ 18 | -111.65170724365224, 19 | 40.576412297284776 20 | ], 21 | [ 22 | -111.65206083065269, 23 | 40.570682704306535 24 | ], 25 | [ 26 | -111.64746419964825, 27 | 40.568623511989045 28 | ], 29 | [ 30 | -111.64498909064608, 31 | 40.571936094694166 32 | ], 33 | [ 34 | -111.63909597397401, 35 | 40.5761437335739 36 | ], 37 | [ 38 | -111.63567796630466, 39 | 40.57435328128577 40 | ], 41 | [ 42 | -111.63414575596967, 43 | 40.57113034642586 44 | ], 45 | [ 46 | -111.63508865463739, 47 | 40.568265385118025 48 | ], 49 | [ 50 | -111.63732803897278, 51 | 40.566206118417426 52 | ], 53 | [ 54 | -111.63603155330458, 55 | 40.563161868970354 56 | ], 57 | [ 58 | -111.63638514030504, 59 | 40.55966976528043 60 | ], 61 | [ 62 | -111.63461720530324, 63 | 40.55841614516092 64 | ], 65 | [ 66 | -111.63391003130286, 67 | 40.55564018849799 68 | ], 69 | [ 70 | -111.62129876162464, 71 | 40.55474469405402 72 | ], 73 | [ 74 | -111.61575923195305, 75 | 40.550893931466305 76 | ], 77 | [ 78 | -111.60774459327926, 79 | 40.55008792987584 80 | ], 81 | [ 82 | -111.60974825135158, 83 | 40.54319174077821 84 | ], 85 | [ 86 | -111.60583201975598, 87 | 40.539063421173324 88 | ] 89 | ], 90 | "type": "LineString" 91 | } 92 | } 93 | ] 94 | } -------------------------------------------------------------------------------- /test/demProfiler/tileCover.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { promisify } from "util"; 3 | import { expect, test } from "vitest"; 4 | import tileCover from "../../src/demProfiler/tileCover"; 5 | 6 | const readFileAsync = promisify(fs.readFile); 7 | 8 | test('Find "tileCover" for a simple line at zooms 13, 14, 15 and 16', async () => { 9 | // path up the mountain in SLC 10 | const coordinates: GeoJSON.Position[] = [ 11 | [-111.88811277644503, 40.79618409829391], 12 | [-111.88645596614953, 40.797821596351326], 13 | [-111.88857300152719, 40.79938937578956], 14 | [-111.88811277644503, 40.80168871865277], 15 | [-111.88502926839493, 40.80350026602076], 16 | [-111.88553551598517, 40.80482405782945], 17 | ]; 18 | 19 | expect(tileCover(coordinates, 13, 512)).toEqual( 20 | JSON.parse( 21 | await readFileAsync("test/fixtures/tileCoverSimple13.json", "utf-8"), 22 | ), 23 | ); 24 | expect(tileCover(coordinates, 14, 512)).toEqual( 25 | JSON.parse( 26 | await readFileAsync("test/fixtures/tileCoverSimple14.json", "utf-8"), 27 | ), 28 | ); 29 | expect(tileCover(coordinates, 15, 512)).toEqual( 30 | JSON.parse( 31 | await readFileAsync("test/fixtures/tileCoverSimple15.json", "utf-8"), 32 | ), 33 | ); 34 | expect(tileCover(coordinates, 16, 512)).toEqual( 35 | JSON.parse( 36 | await readFileAsync("test/fixtures/tileCoverSimple16.json", "utf-8"), 37 | ), 38 | ); 39 | }); 40 | 41 | test('Find "tileCover" for long lines that need to be reduced line at zooms 13, 14, 15 and 16', async () => { 42 | // path up the mountain in SLC 43 | const coordinates: GeoJSON.Position[] = [ 44 | [-111.88858788961196, 40.79298969416331], 45 | [-111.8906434126717, 40.813447493739915], 46 | [-111.85333947566436, 40.818575361886644], 47 | [-111.85419686695758, 40.821268092050104], 48 | [-111.85061873422427, 40.82363013913786], 49 | [-111.84848708068107, 40.821152868089484], 50 | [-111.80737661948963, 40.83780065510172], 51 | ]; 52 | 53 | expect(tileCover(coordinates, 13, 512)).toEqual( 54 | JSON.parse( 55 | await readFileAsync("test/fixtures/tileCoverLong13.json", "utf-8"), 56 | ), 57 | ); 58 | expect(tileCover(coordinates, 14, 512)).toEqual( 59 | JSON.parse( 60 | await readFileAsync("test/fixtures/tileCoverLong14.json", "utf-8"), 61 | ), 62 | ); 63 | expect(tileCover(coordinates, 15, 512)).toEqual( 64 | JSON.parse( 65 | await readFileAsync("test/fixtures/tileCoverLong15.json", "utf-8"), 66 | ), 67 | ); 68 | expect(tileCover(coordinates, 16, 512)).toEqual( 69 | JSON.parse( 70 | await readFileAsync("test/fixtures/tileCoverLong16.json", "utf-8"), 71 | ), 72 | ); 73 | }); 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@maptiler/client", 3 | "version": "2.6.0", 4 | "description": "Javascript & Typescript wrapper to MapTiler Cloud API", 5 | "module": "dist/maptiler-client.mjs", 6 | "types": "dist/maptiler-client.d.ts", 7 | "type": "module", 8 | "author": "MapTiler", 9 | "homepage": "https://github.com/maptiler/maptiler-client-js", 10 | "exports": { 11 | "import": "./dist/maptiler-client.mjs", 12 | "require": "./dist/maptiler-client.cjs", 13 | "types": "./dist/maptiler-client.d.ts" 14 | }, 15 | "publishConfig": { 16 | "access": "public", 17 | "registry": "https://registry.npmjs.org/" 18 | }, 19 | "keywords": [ 20 | "geocoding", 21 | "forward geocoding", 22 | "reverse geocoding", 23 | "batch geocoding", 24 | "coordinates", 25 | "CRS", 26 | "geolocation", 27 | "static maps", 28 | "maptiler", 29 | "map", 30 | "REST API", 31 | "wrapper", 32 | "cloud" 33 | ], 34 | "scripts": { 35 | "build": "rm -rf dist/*; NODE_ENV=production rollup -c", 36 | "dev": "rm -rf dist/*; NODE_ENV=development rollup -c -w", 37 | "devserve": "rm -rf dist/*; NODE_ENV=development rollup -c -w & serve && fg", 38 | "format:fix": "prettier --write \"src/**/*.{js,ts,tsx}\"", 39 | "format": "prettier -c \"src/**/*.{js,ts,tsx}\"", 40 | "lint:fix": "eslint --fix \"src/**/*.{js,ts}\"", 41 | "lint": "eslint \"src/**/*.{js,ts}\"", 42 | "docmd": "rm -rf docsmd/*; typedoc --readme none --plugin typedoc-plugin-markdown --out docsmd src/index.ts", 43 | "dochtml": "rm -rf docs/*; typedoc; cp -r images docs/", 44 | "doc": "npm run docmd; npm run dochtml", 45 | "prepare": "npm run format:fix && npm run lint:fix && npm run test && npm run build", 46 | "test:prod": "vitest test --run", 47 | "test:dev": "vitest test --watch", 48 | "test": "" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "https://github.com/maptiler/maptiler-client-js.git" 53 | }, 54 | "license": "BSD-3-Clause", 55 | "devDependencies": { 56 | "@rollup/plugin-commonjs": "^25.0.7", 57 | "@rollup/plugin-json": "^6.0.1", 58 | "@rollup/plugin-node-resolve": "^15.2.3", 59 | "@rollup/pluginutils": "^5.0.5", 60 | "@types/geojson": "^7946.0.10", 61 | "@typescript-eslint/eslint-plugin": "^6.9.0", 62 | "@typescript-eslint/parser": "^6.9.0", 63 | "eslint": "^8.52.0", 64 | "prettier": "^3.0.3", 65 | "rollup": "^4.1.4", 66 | "rollup-plugin-dts": "^6.1.0", 67 | "rollup-plugin-esbuild": "^6.1.0", 68 | "rollup-plugin-node-globals": "^1.4.0", 69 | "rollup-plugin-string": "^3.0.0", 70 | "rollup-plugin-swc": "^0.2.1", 71 | "serve": "^14.1.1", 72 | "sharp": "^0.32.6", 73 | "typedoc": "^0.25.2", 74 | "typedoc-plugin-markdown": "^3.16.0", 75 | "typescript": "^5.2.2", 76 | "vitest": "^0.34.6" 77 | }, 78 | "dependencies": { 79 | "quick-lru": "^7.0.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/tiledecoding.ts: -------------------------------------------------------------------------------- 1 | import QuickLRU from "quick-lru"; 2 | import { config } from "./config"; 3 | 4 | /** 5 | * Informations about an image 6 | */ 7 | export type PixelData = { 8 | /** 9 | * The array of pixels as RGBRGB or RGBARGBA in a row-major order 10 | */ 11 | pixels: Uint8ClampedArray | Uint8Array; 12 | /** 13 | * Width of the image in number of pixels 14 | */ 15 | width: number; 16 | /** 17 | * Height of the image in number of pixels 18 | */ 19 | height: number; 20 | /** 21 | * Number of components per pixel (3 if image is RGB, 4 if image is RGBA) 22 | */ 23 | components: number; 24 | }; 25 | 26 | /** 27 | * Type for the function that decodes an image file ArrayBuffer into valid pixel data 28 | */ 29 | export type BufferToPixelDataFunction = (ArrayBuffer) => Promise; 30 | 31 | /** 32 | * Main properties necessary from a TileJSON 33 | */ 34 | export type TileJSON = { 35 | scale: string; 36 | format: string; 37 | maxzoom: number; 38 | minzoom: number; 39 | profile: string; 40 | description: string; 41 | attribution: string; 42 | bounds: [number, number, number, number]; 43 | center: [number, number, number]; 44 | 45 | /** 46 | * TileJSON version 47 | */ 48 | tilejson: string; 49 | name: string; 50 | crs: string; 51 | crs_wkt: string; 52 | extent: [number, number, number, number]; 53 | tiles: string[]; 54 | }; 55 | 56 | // The LRU cache for storing tile PixelData 57 | let tileCache = null; 58 | 59 | /** 60 | * Singleton-like function to access the tile cache 61 | */ 62 | export function getTileCache(): QuickLRU { 63 | if (!tileCache) { 64 | tileCache = new QuickLRU({ maxSize: config.tileCacheSize }); 65 | } 66 | return tileCache; 67 | } 68 | 69 | /** 70 | * Browser function to decode an image file buffer into valid pixel data 71 | */ 72 | export async function bufferToPixelDataBrowser( 73 | buff: ArrayBuffer, 74 | ): Promise { 75 | const blob = new Blob([buff]); 76 | const imageBitmap = await createImageBitmap(blob); 77 | 78 | const canvas = document.createElement("canvas"); 79 | const ctx = canvas.getContext("2d"); 80 | canvas.width = imageBitmap.width; 81 | canvas.height = imageBitmap.height; 82 | ctx.drawImage(imageBitmap, 0, 0); 83 | 84 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 85 | return { 86 | pixels: imageData.data, 87 | width: canvas.width, 88 | height: canvas.height, 89 | components: imageData.data.length / (canvas.width * canvas.height), 90 | }; 91 | } 92 | 93 | export function getBufferToPixelDataParser(): BufferToPixelDataFunction { 94 | if (config.bufferToPixelData) { 95 | return config.bufferToPixelData; 96 | } 97 | 98 | if (typeof window !== "undefined") { 99 | return bufferToPixelDataBrowser; 100 | } 101 | 102 | throw new Error( 103 | "An image file buffer to pixel data parser is necessary. Specify it in `config.bufferToPixelData`", 104 | ); 105 | } 106 | 107 | export function canParsePixelData(): boolean { 108 | return !!config.bufferToPixelData || typeof window !== "undefined"; 109 | } 110 | -------------------------------------------------------------------------------- /examples/test-browser-umd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | tslib 4 | 5 | 6 | 7 |

8 | This demo is using the UMD bundle. Add ?key=YOUR_API_KEY to the URL. 9 |

10 |

11 | Open JS console. 12 |

13 |
14 | 15 | 16 | 17 | 89 | 90 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import dts from "rollup-plugin-dts"; 2 | import esbuild from "rollup-plugin-esbuild"; 3 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 4 | import globals from "rollup-plugin-node-globals"; 5 | import commonjs from "@rollup/plugin-commonjs"; 6 | import json from "@rollup/plugin-json"; 7 | 8 | const name = "maptiler-client" 9 | 10 | const bundles = [ 11 | // ES module, not minified + sourcemap 12 | { 13 | plugins: [ 14 | json(), 15 | esbuild(), 16 | ], 17 | output: [ 18 | { 19 | file: `dist/${name}.mjs`, 20 | format: "es", 21 | sourcemap: true 22 | } 23 | ], 24 | input: "src/index.ts", 25 | watch: { 26 | include: "src/**" 27 | }, 28 | external: ["quick-lru"] 29 | }, 30 | 31 | // CJS module, not minified + sourcemap 32 | { 33 | plugins: [ 34 | nodeResolve(), 35 | commonjs({ include: "node_modules/**" }), 36 | globals(), 37 | json(), 38 | esbuild() 39 | ], 40 | output: [ 41 | { 42 | file: `dist/${name}.cjs`, 43 | format: "cjs", 44 | sourcemap: true 45 | } 46 | ], 47 | input: "src/index.ts", 48 | watch: { 49 | include: "src/**" 50 | }, 51 | external: [] // Decided to include QuickLRU to the CJS bundle because it is otherwise not CJS compatible 52 | }, 53 | 54 | // UMD module, not minified 55 | { 56 | plugins: [ 57 | nodeResolve(), // for the standalone UMD, we want to resolve so that the bundle contains all the dep. 58 | commonjs({ include: "node_modules/**" }), 59 | globals(), 60 | json(), 61 | esbuild() 62 | ], 63 | output: [ 64 | { 65 | name: "maptilerClient", 66 | file: `dist/${name}.umd.js`, 67 | format: "umd", 68 | sourcemap: true 69 | } 70 | ], 71 | input: "src/index.ts", 72 | watch: { 73 | include: "src/**" 74 | }, 75 | external: [] 76 | }, 77 | 78 | // types 79 | { 80 | "plugins": [ 81 | dts() 82 | ], 83 | output: { 84 | file: `dist/${name}.d.ts`, 85 | format: "es" 86 | }, 87 | input: "src/index.ts" 88 | } 89 | ] 90 | 91 | if (process.env.NODE_ENV === "production") { 92 | bundles.push( 93 | // ES module, minified 94 | { 95 | plugins: [ 96 | json(), 97 | esbuild({ 98 | sourceMap: false, 99 | minify: true, 100 | }) 101 | ], 102 | output: [ 103 | { 104 | file: `dist/${name}.min.mjs`, 105 | format: "es", 106 | } 107 | ], 108 | input: "src/index.ts", 109 | external: ["quick-lru"], 110 | }, 111 | { 112 | plugins: [ 113 | nodeResolve(), // for the standalone UMD, we want to resolve so that the bundle contains all the dep. 114 | commonjs({ include: "node_modules/**" }), 115 | globals(), 116 | json(), 117 | esbuild({ 118 | sourceMap: false, 119 | minify: true, 120 | }) 121 | ], 122 | output: [ 123 | { 124 | name: "maptilerClient", 125 | file: `dist/${name}.umd.min.js`, 126 | format: "umd", 127 | sourcemap: false 128 | } 129 | ], 130 | input: "src/index.ts", 131 | watch: { 132 | include: "src/**" 133 | }, 134 | external: [] 135 | }) 136 | 137 | } 138 | 139 | export default bundles 140 | 141 | 142 | -------------------------------------------------------------------------------- /examples/test-browser-es.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | tslib 4 | 5 | 6 | 7 |

8 | This demo is using the ES module. 9 |

10 |

11 | Open JS console. 12 |

13 | 14 |
15 | 16 | 99 | 100 | -------------------------------------------------------------------------------- /test/demProfiler/index.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { promisify } from "util"; 3 | import { describe, it, expect } from "vitest"; 4 | import { profileLineString } from "../../src/demProfiler"; 5 | import sharp from "sharp"; 6 | 7 | const readFileAsync = promisify(fs.readFile); 8 | 9 | describe("profile", () => { 10 | it("simple line at zoom 13", async () => { 11 | const coordinates: GeoJSON.Position[] = [ 12 | [-111.88811277644503, 40.79618409829391], 13 | [-111.88645596614953, 40.797821596351326], 14 | [-111.88857300152719, 40.79938937578956], 15 | [-111.88811277644503, 40.80168871865277], 16 | [-111.88502926839493, 40.80350026602076], 17 | [-111.88553551598517, 40.80482405782945], 18 | ]; 19 | 20 | const path = { 21 | type: "Feature" as const, 22 | properties: {}, 23 | geometry: { 24 | type: "LineString" as const, 25 | coordinates, 26 | }, 27 | }; 28 | 29 | const options = { 30 | metric: "m" as "m" | "ft", 31 | zoom: 13, 32 | tileSize: 512, 33 | tileRequest: async (x: number, y: number, z: number) => { 34 | const buf = await sharp(`test/fixtures/${z}-${x}-${y}.webp`) 35 | .raw() 36 | .toBuffer({ resolveWithObject: true }); 37 | return { 38 | channels: buf.info.channels, 39 | image: new Uint8ClampedArray(buf.data), 40 | }; 41 | }, 42 | }; 43 | 44 | expect(await profileLineString(path, options)).toEqual( 45 | JSON.parse( 46 | await readFileAsync("test/fixtures/profileSimple13m.json", "utf-8"), 47 | ), 48 | ); 49 | 50 | options.metric = "ft" as const; 51 | 52 | expect(await profileLineString(path, options)).toEqual( 53 | JSON.parse( 54 | await readFileAsync("test/fixtures/profileSimple13ft.json", "utf-8"), 55 | ), 56 | ); 57 | }); 58 | 59 | it("simple line at zoom 14", async () => { 60 | const coordinates: GeoJSON.Position[] = [ 61 | [-111.88811277644503, 40.79618409829391], 62 | [-111.88645596614953, 40.797821596351326], 63 | [-111.88857300152719, 40.79938937578956], 64 | [-111.88811277644503, 40.80168871865277], 65 | [-111.88502926839493, 40.80350026602076], 66 | [-111.88553551598517, 40.80482405782945], 67 | ]; 68 | 69 | const path = { 70 | type: "Feature" as const, 71 | properties: {}, 72 | geometry: { 73 | type: "LineString" as const, 74 | coordinates, 75 | }, 76 | }; 77 | 78 | const options = { 79 | metric: "m" as "m" | "ft", 80 | zoom: 13, 81 | tileSize: 512, 82 | tileRequest: async (x: number, y: number, z: number) => { 83 | const buf = await sharp(`test/fixtures/${z}-${x}-${y}.webp`) 84 | .raw() 85 | .toBuffer({ resolveWithObject: true }); 86 | return { 87 | channels: buf.info.channels, 88 | image: new Uint8ClampedArray(buf.data), 89 | }; 90 | }, 91 | }; 92 | 93 | expect(await profileLineString(path, options)).toEqual( 94 | JSON.parse( 95 | await readFileAsync("test/fixtures/profileSimple14m.json", "utf-8"), 96 | ), 97 | ); 98 | 99 | options.metric = "ft" as const; 100 | 101 | expect(await profileLineString(path, options)).toEqual( 102 | JSON.parse( 103 | await readFileAsync("test/fixtures/profileSimple14ft.json", "utf-8"), 104 | ), 105 | ); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /test/fixtures/gdal.gpx.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "name": "track name" 8 | }, 9 | "geometry": { 10 | "type": "MultiLineString", 11 | "coordinates": [ 12 | [ 13 | [ 14 | 15, 15 | 14, 16 | 16 17 | ], 18 | [ 19 | 18, 20 | 17, 21 | 19 22 | ] 23 | ], 24 | [ 25 | [ 26 | 21, 27 | 20, 28 | 22 29 | ], 30 | [ 31 | 24, 32 | 23, 33 | 25 34 | ] 35 | ] 36 | ] 37 | } 38 | }, 39 | { 40 | "type": "Feature", 41 | "properties": { 42 | "name": "route name", 43 | "time": "2007-11-25T17:58:00Z" 44 | }, 45 | "geometry": { 46 | "type": "LineString", 47 | "coordinates": [ 48 | [ 49 | 6, 50 | 5, 51 | 7 52 | ], 53 | [ 54 | 9, 55 | 8, 56 | 10 57 | ], 58 | [ 59 | 12, 60 | 11, 61 | 13 62 | ] 63 | ] 64 | } 65 | }, 66 | { 67 | "type": "Feature", 68 | "properties": { 69 | "name": "waypoint name", 70 | "cmt": "waypoint comment", 71 | "desc": "waypoint description", 72 | "time": "2007-11-25T17:58:00+01:00", 73 | "links": [ 74 | { 75 | "href": "href", 76 | "text": "text", 77 | "type": "type" 78 | }, 79 | { 80 | "href": "href2", 81 | "text": "text2", 82 | "type": "type2" 83 | }, 84 | { 85 | "href": "href3", 86 | "text": "text3", 87 | "type": "type3" 88 | } 89 | ], 90 | "type": "type" 91 | }, 92 | "geometry": { 93 | "type": "Point", 94 | "coordinates": [ 95 | 1, 96 | 0, 97 | 2 98 | ] 99 | } 100 | }, 101 | { 102 | "type": "Feature", 103 | "properties": {}, 104 | "geometry": { 105 | "type": "Point", 106 | "coordinates": [ 107 | 4, 108 | 3 109 | ] 110 | } 111 | } 112 | ] 113 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # MapTiler Client Changelog 2 | 3 | ## 2.6.0 4 | ### New Features 5 | - Bumps 'Streets' & 'Landscape' styles (and their variants) to new v4 styles. 6 | - Adds the new 'Base' style and its variants. 7 | - Namespaces V4 and V2 Styles and adds mechanism for switching to defaults. 8 | 9 | ### Bug Fixes 10 | None 11 | 12 | ### Others 13 | - Adds custom deprecation warnings. 14 | - Deprecates the older v2 styles and adds warnings regarding their usage. 15 | 16 | ## 2.5.1 17 | ### New Features 18 | None 19 | 20 | ### Bug Fixes 21 | None 22 | 23 | ### Others 24 | `Hybrid` map style is no longer deprecated. 25 | 26 | ## 2.5.0 27 | ### New Features 28 | - `at` and `batch` functions compute elevation on the server using MapTiler Elevation API by default 29 | - Elevation supports Node.js: computed on server when `bufferToPixelData` is not provided 30 | - Added `computeOn` option to force client/server elevation processing 31 | - Added `canParsePixelData` function to check if elevation can be computed on the client 32 | 33 | ### Bug Fixes 34 | - `bufferToPixelData` can be undefined 35 | 36 | ### Others 37 | None 38 | 39 | ## 2.4.0 40 | ### New Features 41 | - Added `elevation` option to Geolocation API 42 | 43 | ### Bug Fixes 44 | None 45 | 46 | ### Others 47 | None 48 | 49 | ## 2.3.2 50 | ### New Features 51 | None 52 | 53 | ### Bug Fixes 54 | None 55 | 56 | ### Others 57 | Added deprecation warning and field for styles that will be deprecated in the future. 58 | 59 | ## 2.3.1 60 | ### New Features 61 | None 62 | 63 | ### Bug Fixes 64 | - Fixed default export for new styles 65 | 66 | ## 2.3.0 67 | ### New Features 68 | - Additional new map styles as part of the MapStyle Object 69 | 70 | ### Bug Fixes 71 | None 72 | 73 | ### Others 74 | None 75 | 76 | ## 2.2.1 77 | ### Bug Fixes 78 | - Fixed incorrect `geocoding: false` for some more languages 79 | 80 | ## 2.2.0 81 | ### New Features 82 | - Exposing ISO languages and non-ISO language separately 83 | ### Others 84 | - fixing typos 85 | - languages are now ordered alphabetically 86 | 87 | ## 2.1.0 88 | ### New Features 89 | - Added `continental_marine` and `major_landform` to geocoding type 90 | ### Bug Fixes 91 | - Fixed Czech language geocoding flag 92 | ### Others 93 | - Languages are now listed in the Client library 94 | - Improved geocoding types and limit documentation 95 | 96 | ## 2.0.0 97 | ### New Features 98 | - Added `matching_text` and `matching_place_name` properties to geocoding feature response 99 | - Added `road` to geocoding `types` 100 | ### Others 101 | - Languages are now listed in the Client library 102 | 103 | ## 1.8.1 104 | ### Bug Fixes 105 | - The QuickLRU dependency is not CJS compatible to it is now fully bundled into the CJS bundle 106 | ### Others 107 | - Moved somes wrongly positioned deps into devDep 108 | 109 | ## 1.8.0 110 | ### New Features 111 | - Rework of the elevation API to be improve developper experience (new module `elevation`) 112 | - Expoing some geo math with the new `math` module 113 | - synchronized geocoding types with current geocoding API 114 | - added `excludeTypes` option to geocoding API 115 | 116 | ## 1.7.0 117 | ### New Features 118 | - DEM elevation API (https://github.com/maptiler/maptiler-client-js/pull/24) 119 | ### Bug Fixes 120 | - `geocoding.byId` can now be used with the apiKey (https://github.com/maptiler/maptiler-client-js/pull/27) 121 | - the Typescript types are now properly exported (https://github.com/maptiler/maptiler-client-js/pull/25) 122 | ### Others 123 | - the Typescript `moduleResolution` is now `"Bundler"` (used to be `"Node"`) (https://github.com/maptiler/maptiler-client-js/pull/28) 124 | - updated some dev-dependencies (https://github.com/maptiler/maptiler-client-js/pull/28) 125 | -------------------------------------------------------------------------------- /test/geometry/util.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, test } from "vitest"; 2 | import { 3 | earthRadius, 4 | xyzToTileID, 5 | mToFt, 6 | degToRad, 7 | getZoomLevelResolution, 8 | } from "../../src/geometry"; 9 | 10 | test("earthRadius", () => { 11 | expect(earthRadius).toEqual(6371008.8); 12 | }); 13 | 14 | describe("xyzToTileID", () => { 15 | it("managing tile x-y-z to ID", () => { 16 | expect(xyzToTileID(0, 0, 0)).toEqual(0); 17 | expect(xyzToTileID(0, 0, 1)).toEqual(1); 18 | expect(xyzToTileID(1, 0, 1)).toEqual(33); 19 | expect(xyzToTileID(1, 1, 1)).toEqual(97); 20 | expect(xyzToTileID(1048575, 1048575, 20)).toEqual(35184372088820); 21 | }); 22 | 23 | it("xyzToTileID for all zooms 1-7", () => { 24 | const idCache = new Set(); 25 | for (let z = 1; z <= 7; z++) { 26 | for (let x = 0; x < 2 ** z; x++) { 27 | for (let y = 0; y < 2 ** z; y++) { 28 | const id = xyzToTileID(x, y, z); 29 | if (idCache.has(id)) throw new Error(`duplicate id ${id}`); 30 | idCache.add(id); 31 | } 32 | } 33 | } 34 | }); 35 | }); 36 | 37 | // test mToFt 38 | describe("mToFt", () => { 39 | it("converts km to miles", () => { 40 | expect(mToFt(1)).toEqual(3.28084); 41 | expect(mToFt(10)).toEqual(32.8084); 42 | expect(mToFt(100)).toEqual(328.084); 43 | expect(mToFt(1000)).toEqual(3280.84); 44 | }); 45 | }); 46 | 47 | // test degToRad 48 | describe("degToRad", () => { 49 | it("converts degrees to radians", () => { 50 | expect(degToRad(0)).toEqual(0); 51 | expect(degToRad(180)).toEqual(Math.PI); 52 | expect(degToRad(360)).toEqual(0); 53 | expect(degToRad(90)).toEqual(Math.PI / 2); 54 | expect(degToRad(270)).toEqual((3 * Math.PI) / 2); 55 | }); 56 | }); 57 | 58 | describe('getZoomLevelResolution', () => { 59 | it('calculates resolution of a zoom level', () => { 60 | expect(getZoomLevelResolution(0, 0)).toBeCloseTo(234814.55089206144) 61 | expect(getZoomLevelResolution(0, 1)).toBeCloseTo(117407.27544603072) 62 | expect(getZoomLevelResolution(0, 2)).toBeCloseTo(58703.63772301536) 63 | expect(getZoomLevelResolution(0, 3)).toBeCloseTo(29351.81886150768) 64 | expect(getZoomLevelResolution(0, 4)).toBeCloseTo(14675.90943075384) 65 | expect(getZoomLevelResolution(0, 5)).toBeCloseTo(7337.95471537692) 66 | expect(getZoomLevelResolution(0, 6)).toBeCloseTo(3668.97735768846) 67 | expect(getZoomLevelResolution(0, 7)).toBeCloseTo(1834.48867884423) 68 | expect(getZoomLevelResolution(0, 8)).toBeCloseTo(917.244339422115) 69 | expect(getZoomLevelResolution(0, 9)).toBeCloseTo(458.6221697110575) 70 | expect(getZoomLevelResolution(0, 10)).toBeCloseTo(229.3110848555287) 71 | expect(getZoomLevelResolution(0, 11)).toBeCloseTo(114.65554242776437) 72 | expect(getZoomLevelResolution(0, 12)).toBeCloseTo(57.32777121388218) 73 | expect(getZoomLevelResolution(0, 13)).toBeCloseTo(28.663885606941093) 74 | expect(getZoomLevelResolution(0, 14)).toBeCloseTo(14.33194280347054) 75 | expect(getZoomLevelResolution(0, 15)).toBeCloseTo(7.165971401735273) 76 | expect(getZoomLevelResolution(0, 16)).toBeCloseTo(3.582985700867636) 77 | expect(getZoomLevelResolution(0, 17)).toBeCloseTo(1.7914928504338183) 78 | expect(getZoomLevelResolution(0, 18)).toBeCloseTo(0.8957464252169091) 79 | expect(getZoomLevelResolution(0, 19)).toBeCloseTo(0.4478732126084545) 80 | }) 81 | 82 | it('calcuates resolution of the zoom level 15 at various latitudes', () => { 83 | expect(getZoomLevelResolution(0, 15)).toBeCloseTo(7.165971401735273) 84 | expect(getZoomLevelResolution(45, 15)).toBeCloseTo(5.067106971955882) 85 | expect(getZoomLevelResolution(60, 15)).toBeCloseTo(3.5829857008676376) 86 | expect(getZoomLevelResolution(75, 15)).toBeCloseTo(1.8546898754290955) 87 | expect(getZoomLevelResolution(85, 15)).toBeCloseTo(0.6245555600267148) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /src/misc.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GeoJsonObject, 3 | GeometryObject, 4 | LineString, 5 | MultiLineString, 6 | Feature, 7 | FeatureCollection, 8 | Position, 9 | } from "geojson"; 10 | 11 | /** 12 | * From a generic GeoJSON object extract thepossibly nested LineString and MultiLineString features 13 | * it contains. The result is a flat array made of LineString and MultiLineString. 14 | */ 15 | function extractLineStrings( 16 | geoJson: GeoJsonObject, 17 | ): Array { 18 | const lineStrings: Array = []; 19 | 20 | function extractFromGeometry(geometry: GeometryObject) { 21 | if (geometry.type === "LineString" || geometry.type === "MultiLineString") { 22 | lineStrings.push(geometry as LineString | MultiLineString); 23 | } 24 | } 25 | 26 | function extractFromFeature(feature: Feature) { 27 | if (feature.geometry) { 28 | extractFromGeometry(feature.geometry); 29 | } 30 | } 31 | 32 | function extractFromFeatureCollection(collection: FeatureCollection) { 33 | for (const feature of collection.features) { 34 | if (feature.type === "Feature") { 35 | extractFromFeature(feature); 36 | } else if (feature.type === "FeatureCollection") { 37 | extractFromFeatureCollection(feature as unknown as FeatureCollection); // had to add unknown 38 | } 39 | } 40 | } 41 | 42 | if (geoJson.type === "Feature") { 43 | extractFromFeature(geoJson as Feature); 44 | } else if (geoJson.type === "FeatureCollection") { 45 | extractFromFeatureCollection(geoJson as FeatureCollection); 46 | } else { 47 | // It's a single geometry 48 | extractFromGeometry(geoJson as GeometryObject); 49 | } 50 | 51 | return lineStrings; 52 | } 53 | 54 | // square distance from a point to a segment 55 | function getSqSegDist(p: Position, p1: Position, p2: Position): number { 56 | let x = p1[0], 57 | y = p1[1], 58 | dx = p2[0] - x, 59 | dy = p2[1] - y; 60 | 61 | if (dx !== 0 || dy !== 0) { 62 | const t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy); 63 | 64 | if (t > 1) { 65 | x = p2[0]; 66 | y = p2[1]; 67 | } else if (t > 0) { 68 | x += dx * t; 69 | y += dy * t; 70 | } 71 | } 72 | 73 | dx = p[0] - x; 74 | dy = p[1] - y; 75 | 76 | return dx * dx + dy * dy; 77 | } 78 | 79 | function simplifyDPStep( 80 | points: Array, 81 | first: number, 82 | last: number, 83 | sqTolerance: number, 84 | simplified: Array, 85 | ) { 86 | let maxSqDist = sqTolerance, 87 | index; 88 | 89 | for (let i = first + 1; i < last; i++) { 90 | const sqDist = getSqSegDist(points[i], points[first], points[last]); 91 | 92 | if (sqDist > maxSqDist) { 93 | index = i; 94 | maxSqDist = sqDist; 95 | } 96 | } 97 | 98 | if (maxSqDist > sqTolerance) { 99 | if (index - first > 1) { 100 | simplifyDPStep(points, first, index, sqTolerance, simplified); 101 | } 102 | simplified.push(points[index]); 103 | 104 | if (last - index > 1) { 105 | simplifyDPStep(points, index, last, sqTolerance, simplified); 106 | } 107 | } 108 | } 109 | 110 | // simplification using Ramer-Douglas-Peucker algorithm 111 | function simplifyDouglasPeucker( 112 | points: Array, 113 | sqTolerance: number, 114 | ): Array { 115 | const last = points.length - 1; 116 | const simplified = [points[0]]; 117 | simplifyDPStep(points, 0, last, sqTolerance, simplified); 118 | simplified.push(points[last]); 119 | return simplified; 120 | } 121 | 122 | /** 123 | * Simplify a path made of a list of GeoJSON Positions, with a tolerance. 124 | */ 125 | function simplify(points: Array, tolerance: number): Array { 126 | if (points.length <= 2) { 127 | return points; 128 | } 129 | 130 | const sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1; 131 | const simplePoints = simplifyDouglasPeucker(points, sqTolerance); 132 | return simplePoints; 133 | } 134 | 135 | export const misc = { 136 | extractLineStrings, 137 | simplify, 138 | }; 139 | -------------------------------------------------------------------------------- /src/services/geolocation.ts: -------------------------------------------------------------------------------- 1 | import { BBox } from "geojson"; 2 | import { callFetch } from "../callFetch"; 3 | import { config } from "../config"; 4 | import { defaults } from "../defaults"; 5 | import { ServiceError } from "./ServiceError"; 6 | 7 | const customMessages = { 8 | 403: "Key is missing, invalid or restricted", 9 | }; 10 | 11 | /** 12 | * Options that can be provided to get user data. 13 | */ 14 | export type GeolocationInfoOptions = { 15 | /** 16 | * Custom MapTiler Cloud API key to use instead of the one in global `config` 17 | */ 18 | apiKey?: string; 19 | 20 | /** 21 | * Include elevation (in meters) in the results. 22 | * Default: `false` 23 | */ 24 | elevation?: boolean; 25 | }; 26 | 27 | export type GeolocationResult = { 28 | /** 29 | * Name of the country 30 | * Example: Switzerland 31 | */ 32 | country?: string; 33 | 34 | /** 35 | * Two-letter code of the country ISO 3166-1 alpha-2 codes 36 | * Example: CH 37 | */ 38 | country_code?: string; 39 | 40 | /** 41 | * Bounds of the country in WGS84 degrees [west, south, east, north]. 42 | * Example: [5.95538,45.818852,10.490936,47.809357] 43 | */ 44 | country_bounds?: BBox; 45 | 46 | /** 47 | * Official country languages in ISO 639-1 format. ISO 639-1 codes 48 | * Example: ["de","fr","it"] 49 | */ 50 | country_languages?: Array; 51 | 52 | /** 53 | * Name of the continent 54 | * Example: Europe 55 | */ 56 | continent?: string; 57 | 58 | /** 59 | * Two-letter code of the continent 60 | * Example: EU 61 | */ 62 | continent_code?: string; 63 | 64 | /** 65 | * Indicated whether the country is part of the European Union. 66 | */ 67 | eu?: boolean; 68 | 69 | /** 70 | * Name of the city 71 | * Example: Zurich 72 | */ 73 | city?: string; 74 | 75 | /** 76 | * Latitude of the location 77 | * Example: 47.36667 78 | */ 79 | latitude?: number; 80 | 81 | /** 82 | * Longitude of the location 83 | * Example: 8.55 84 | */ 85 | longitude?: number; 86 | 87 | /** 88 | * Postal code 89 | * Example: 8000 90 | */ 91 | postal?: string; 92 | 93 | /** 94 | * If known, the ISO 3166-2 name for the first level region. ISO 3166-2 codes 95 | * Example: Zurich 96 | */ 97 | region?: string; 98 | 99 | /** 100 | * If known, the ISO 3166-2 code for the first level region. ISO 3166-2 codes 101 | * Example: ZH 102 | */ 103 | region_code?: string; 104 | 105 | /** 106 | * Name of the timezone 107 | * Example: Europe/Zurich 108 | */ 109 | timezone?: string; 110 | 111 | /** 112 | * Elevation of the location in meters 113 | * Example: 433 114 | */ 115 | elevation?: number; 116 | }; 117 | 118 | /** 119 | * Looks up geolocation details from IP address using MapTiler API. 120 | * Learn more on the MapTiler API reference page: https://docs.maptiler.com/cloud/api/geolocation/#ip-geolocation 121 | * @returns 122 | */ 123 | async function info( 124 | options: GeolocationInfoOptions = {}, 125 | ): Promise { 126 | const endpoint = new URL(`geolocation/ip.json`, defaults.maptilerApiURL); 127 | endpoint.searchParams.set("key", options.apiKey ?? config.apiKey); 128 | 129 | if ("elevation" in options) { 130 | endpoint.searchParams.set( 131 | "elevation", 132 | options.elevation ? "true" : "false", 133 | ); 134 | } 135 | 136 | const urlWithParams = endpoint.toString(); 137 | 138 | const res = await callFetch(urlWithParams); 139 | 140 | if (!res.ok) { 141 | throw new ServiceError( 142 | res, 143 | res.status in customMessages ? customMessages[res.status] : "", 144 | ); 145 | } 146 | 147 | const obj = await res.json(); 148 | return obj as GeolocationResult; 149 | } 150 | 151 | /** 152 | * The **geolocation** namespace contains an asynchronous function to call the [MapTiler Geolocation API](https://docs.maptiler.com/cloud/api/geolocation/). 153 | * The **Geolocation API** provides a way to retrieve the IP address as well as geographic informations of a machine performing the query (most likely: a user) 154 | */ 155 | const geolocation = { 156 | info, 157 | }; 158 | 159 | export { geolocation }; 160 | -------------------------------------------------------------------------------- /examples/elevation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | tslib 4 | 5 | 6 | 7 |

8 | This demo is using the UMD bundle. Add ?key=YOUR_API_KEY to the URL. 9 |

10 |

11 | Open JS console. 12 |

13 |
14 | 15 | 16 | 17 | 113 | 114 | -------------------------------------------------------------------------------- /test/fixtures/style_url.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Polygon", 8 | "coordinates": [ 9 | [ 10 | [ 11 | -122.0857412771483, 12 | 37.42227033155257, 13 | 17 14 | ], 15 | [ 16 | -122.0858169768481, 17 | 37.42231408832346, 18 | 17 19 | ], 20 | [ 21 | -122.085852582875, 22 | 37.42230337469744, 23 | 17 24 | ], 25 | [ 26 | -122.0858799945639, 27 | 37.42225686138789, 28 | 17 29 | ], 30 | [ 31 | -122.0858860101409, 32 | 37.4222311076138, 33 | 17 34 | ], 35 | [ 36 | -122.0858069157288, 37 | 37.42220250173855, 38 | 17 39 | ], 40 | [ 41 | -122.0858379542653, 42 | 37.42214027058678, 43 | 17 44 | ], 45 | [ 46 | -122.0856732640519, 47 | 37.42208690214408, 48 | 17 49 | ], 50 | [ 51 | -122.0856022926407, 52 | 37.42214885429042, 53 | 17 54 | ], 55 | [ 56 | -122.0855902778436, 57 | 37.422128290487, 58 | 17 59 | ], 60 | [ 61 | -122.0855841672237, 62 | 37.42208171967246, 63 | 17 64 | ], 65 | [ 66 | -122.0854852065741, 67 | 37.42210455874995, 68 | 17 69 | ], 70 | [ 71 | -122.0855067264352, 72 | 37.42214267949824, 73 | 17 74 | ], 75 | [ 76 | -122.0854430712915, 77 | 37.42212783846172, 78 | 17 79 | ], 80 | [ 81 | -122.0850990714904, 82 | 37.42251282407603, 83 | 17 84 | ], 85 | [ 86 | -122.0856769818632, 87 | 37.42281815323651, 88 | 17 89 | ], 90 | [ 91 | -122.0860162273783, 92 | 37.42244918858722, 93 | 17 94 | ], 95 | [ 96 | -122.0857260327004, 97 | 37.42229239604253, 98 | 17 99 | ], 100 | [ 101 | -122.0857412771483, 102 | 37.42227033155257, 103 | 17 104 | ] 105 | ] 106 | ] 107 | }, 108 | "properties": { 109 | "name": "Building 41", 110 | "styleUrl": "#transBluePoly", 111 | "styleHash": "-109facd6", 112 | "stroke": "#000000", 113 | "stroke-opacity": 1, 114 | "stroke-width": 1.5, 115 | "fill": "#0000ff", 116 | "fill-opacity": 0.49019607843137253 117 | } 118 | } 119 | ] 120 | } -------------------------------------------------------------------------------- /examples/linestring.geojson: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "type": "FeatureCollection", 4 | "features": [ 5 | { 6 | "type": "Feature", 7 | "geometry": { 8 | "type": "LineString", 9 | "coordinates": [ 10 | [ 11 | 6.249869, 12 | 46.402238, 13 | 424.3 14 | ], 15 | [ 16 | 6.249877, 17 | 46.40224, 18 | 424.4 19 | ], 20 | [ 21 | 6.249877, 22 | 46.402237, 23 | 424.4 24 | ], 25 | [ 26 | 6.249871, 27 | 46.402232, 28 | 424.5 29 | ], 30 | [ 31 | 6.249863, 32 | 46.402227, 33 | 424.6 34 | ], 35 | [ 36 | 6.249856, 37 | 46.402223, 38 | 424.7 39 | ], 40 | [ 41 | 6.249849, 42 | 46.402224, 43 | 424.8 44 | ], 45 | [ 46 | 6.249846, 47 | 46.402226, 48 | 424.8 49 | ], 50 | [ 51 | 6.249844, 52 | 46.402228, 53 | 424.8 54 | ], 55 | [ 56 | 6.249843, 57 | 46.402228, 58 | 424.8 59 | ], 60 | [ 61 | 6.249834, 62 | 46.402226, 63 | 424.9 64 | ], 65 | [ 66 | 6.249828, 67 | 46.402225, 68 | 425 69 | ], 70 | [ 71 | 6.249812, 72 | 46.402222, 73 | 425.2 74 | ], 75 | [ 76 | 6.249803, 77 | 46.402219, 78 | 425.3 79 | ], 80 | [ 81 | 6.249793, 82 | 46.402217, 83 | 425.4 84 | ], 85 | [ 86 | 6.249783, 87 | 46.402214, 88 | 425.5 89 | ], 90 | [ 91 | 6.249773, 92 | 46.402211, 93 | 425.6 94 | ], 95 | [ 96 | 6.249743, 97 | 46.402183, 98 | 426.1 99 | ], 100 | [ 101 | 6.249713, 102 | 46.402158, 103 | 426.5 104 | ], 105 | [ 106 | 6.24968, 107 | 46.402124, 108 | 427.1 109 | ], 110 | [ 111 | 6.249654, 112 | 46.40209, 113 | 427.6 114 | ], 115 | [ 116 | 6.249629, 117 | 46.402061, 118 | 428 119 | ], 120 | [ 121 | 6.249609, 122 | 46.402035, 123 | 428.4 124 | ], 125 | [ 126 | 6.249576, 127 | 46.402004, 128 | 428.8 129 | ], 130 | [ 131 | 6.249564, 132 | 46.401996, 133 | 428.9 134 | ], 135 | [ 136 | 6.249515, 137 | 46.401992, 138 | 429.5 139 | ], 140 | [ 141 | 6.249496, 142 | 46.401995, 143 | 429.5 144 | ], 145 | [ 146 | 6.249457, 147 | 46.402003, 148 | 429.6 149 | ], 150 | [ 151 | 6.249411, 152 | 46.402019, 153 | 429.6 154 | ] 155 | ] 156 | }, 157 | "id": "d22fc9f8-ba40-4f46-b46d-939564068e0a", 158 | "properties": { 159 | "name": "It's been a while", 160 | "type": "1" 161 | } 162 | } 163 | ] 164 | } -------------------------------------------------------------------------------- /test/fixtures/multitrack.kml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 1 7 | 1 8 | 9 | 10 | 17 | 22 | 27 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 2013-08-08T15:20:40.000Z 52 | #start 53 | 54 | 3.682555,51.050362,50.599998474121094 55 | 56 | 57 | 58 | 59 | 60 | #track 61 | 62 | 63 | 64 | 65 | absolute 66 | 1 67 | 68 | 3.68217 51.050583 54.599998474121094 69 | 2013-08-08T15:24:47.000Z 70 | 3.68211 51.050596 54.79999923706055 71 | 2013-08-08T15:24:48.000Z 72 | 3.68209 51.050598 55.400001525878906 73 | 2013-08-08T15:24:50.000Z 74 | 3.682043 51.050609 55.70000076293945 75 | 2013-08-08T15:24:51.000Z 76 | 3.682012 51.050618 54.5 77 | 2013-08-08T15:24:52.000Z 78 | 3.68198 51.050629 54.29999923706055 79 | 2013-08-08T15:24:53.000Z 80 | 3.681945 51.050638 54.79999923706055 81 | 2013-08-08T15:24:54.000Z 82 | 3.681907 51.050647 54.900001525878906 83 | 2013-08-08T15:24:55.000Z 84 | 3.68187 51.050656 54.599998474121094 85 | 2013-08-08T15:24:56.000Z 86 | 3.681834 51.050664 54.0 87 | 2013-08-08T15:24:57.000Z 88 | 3.681797 51.050669 55.29999923706055 89 | 2013-08-08T15:24:58.000Z 90 | 3.681759 51.050673 55.400001525878906 91 | 2013-08-08T15:24:59.000Z 92 | 3.681721 51.050677 55.20000076293945 93 | 2013-08-08T15:25:00.000Z 94 | 3.681682 51.050682 54.70000076293945 95 | 2013-08-08T15:25:01.000Z 96 | 3.681637 51.050685 55.20000076293945 97 | 2013-08-08T15:25:02.000Z 98 | 3.681589 51.050687 55.0 99 | 2013-08-08T15:25:03.000Z 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 2013-08-08T16:25:57.000Z 111 | #end 112 | 113 | 3.682725,51.050615,61.5 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /test/fixtures/non_gx_multitrack.kml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 1 7 | 1 8 | 9 | 10 | 17 | 22 | 27 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 2013-08-08T15:20:40.000Z 52 | #start 53 | 54 | 3.682555,51.050362,50.599998474121094 55 | 56 | 57 | 58 | 59 | 60 | #track 61 | 62 | 63 | 64 | 65 | absolute 66 | 1 67 | 68 | 3.68217 51.050583 54.599998474121094 69 | 2013-08-08T15:24:47.000Z 70 | 3.68211 51.050596 54.79999923706055 71 | 2013-08-08T15:24:48.000Z 72 | 3.68209 51.050598 55.400001525878906 73 | 2013-08-08T15:24:50.000Z 74 | 3.682043 51.050609 55.70000076293945 75 | 2013-08-08T15:24:51.000Z 76 | 3.682012 51.050618 54.5 77 | 2013-08-08T15:24:52.000Z 78 | 3.68198 51.050629 54.29999923706055 79 | 2013-08-08T15:24:53.000Z 80 | 3.681945 51.050638 54.79999923706055 81 | 2013-08-08T15:24:54.000Z 82 | 3.681907 51.050647 54.900001525878906 83 | 2013-08-08T15:24:55.000Z 84 | 3.68187 51.050656 54.599998474121094 85 | 2013-08-08T15:24:56.000Z 86 | 3.681834 51.050664 54.0 87 | 2013-08-08T15:24:57.000Z 88 | 3.681797 51.050669 55.29999923706055 89 | 2013-08-08T15:24:58.000Z 90 | 3.681759 51.050673 55.400001525878906 91 | 2013-08-08T15:24:59.000Z 92 | 3.681721 51.050677 55.20000076293945 93 | 2013-08-08T15:25:00.000Z 94 | 3.681682 51.050682 54.70000076293945 95 | 2013-08-08T15:25:01.000Z 96 | 3.681637 51.050685 55.20000076293945 97 | 2013-08-08T15:25:02.000Z 98 | 3.681589 51.050687 55.0 99 | 2013-08-08T15:25:03.000Z 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 2013-08-08T16:25:57.000Z 111 | #end 112 | 113 | 3.682725,51.050615,61.5 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /test/fixtures/missing_hr.gpx: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | Garmin International 13 | 14 | 15 | 16 | missing hr 17 | 18 | 19 | 434.94 20 | 21 | 22 | 23 | 434.98 24 | 25 | 26 | 27 | 435.04 28 | 29 | 30 | 31 | 32 | 33 | 434.94 34 | 35 | 36 | 37 | 104 38 | 39 | 40 | 41 | 42 | 434.98 43 | 44 | 45 | 46 | 104 47 | 48 | 49 | 50 | 51 | 435.04 52 | 53 | 54 | 55 | 104 56 | 57 | 58 | 59 | 60 | 435.11 61 | 62 | 63 | 64 | 105 65 | 66 | 67 | 68 | 69 | 435.22 70 | 71 | 72 | 73 | 105 74 | 75 | 76 | 77 | 78 | 436.63 79 | 80 | 81 | 82 | 437.70 83 | 84 | 85 | 86 | 107 87 | 88 | 89 | 90 | 91 | 439.19 92 | 93 | 94 | 95 | 116 96 | 97 | 98 | 99 | 100 | 440.16 101 | 102 | 103 | 104 | 120 105 | 106 | 107 | 108 | 109 | 440.70 110 | 111 | 112 | 113 | 131 114 | 115 | 116 | 117 | 118 | 119 | 120 | 434.94 121 | 122 | 123 | 124 | 434.98 125 | 126 | 127 | 128 | 435.04 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /images/maptiler-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/fixtures/inline_style.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "LineString", 8 | "coordinates": [ 9 | [ 10 | 2.3101624, 11 | 48.7301875 12 | ], 13 | [ 14 | 2.3098714, 15 | 48.7300247 16 | ], 17 | [ 18 | 2.3098051, 19 | 48.7299542 20 | ], 21 | [ 22 | 2.3098493, 23 | 48.7298813 24 | ], 25 | [ 26 | 2.309934, 27 | 48.7298108 28 | ], 29 | [ 30 | 2.3100372, 31 | 48.729806 32 | ], 33 | [ 34 | 2.3101293, 35 | 48.7298424 36 | ], 37 | [ 38 | 2.3101772, 39 | 48.7298862 40 | ], 41 | [ 42 | 2.3101661, 43 | 48.7299542 44 | ], 45 | [ 46 | 2.3101587, 47 | 48.7300538 48 | ], 49 | [ 50 | 2.3101624, 51 | 48.7301875 52 | ] 53 | ] 54 | }, 55 | "properties": { 56 | "name": "With all inline styles", 57 | "stroke": "#ff0000", 58 | "stroke-opacity": 0, 59 | "stroke-width": 3, 60 | "fill": "#ff1100", 61 | "fill-opacity": 1 62 | } 63 | }, 64 | { 65 | "type": "Feature", 66 | "geometry": { 67 | "type": "LineString", 68 | "coordinates": [ 69 | [ 70 | 2.3101624, 71 | 48.7301875 72 | ], 73 | [ 74 | 2.3098714, 75 | 48.7300247 76 | ], 77 | [ 78 | 2.3098051, 79 | 48.7299542 80 | ], 81 | [ 82 | 2.3098493, 83 | 48.7298813 84 | ], 85 | [ 86 | 2.309934, 87 | 48.7298108 88 | ], 89 | [ 90 | 2.3100372, 91 | 48.729806 92 | ], 93 | [ 94 | 2.3101293, 95 | 48.7298424 96 | ], 97 | [ 98 | 2.3101772, 99 | 48.7298862 100 | ], 101 | [ 102 | 2.3101661, 103 | 48.7299542 104 | ], 105 | [ 106 | 2.3101587, 107 | 48.7300538 108 | ], 109 | [ 110 | 2.3101624, 111 | 48.7301875 112 | ] 113 | ] 114 | }, 115 | "properties": { 116 | "name": "With some inline styles", 117 | "fill": "#000000", 118 | "stroke": "#ff0000", 119 | "stroke-opacity": 1, 120 | "fill-opacity": 1 121 | } 122 | }, 123 | { 124 | "type": "Feature", 125 | "geometry": { 126 | "type": "LineString", 127 | "coordinates": [ 128 | [ 129 | 2.3131898, 130 | 48.7315075 131 | ], 132 | [ 133 | 2.3123173, 134 | 48.7309455 135 | ], 136 | [ 137 | 2.312019, 138 | 48.7308143 139 | ], 140 | [ 141 | 2.3114185, 142 | 48.730671 143 | ], 144 | [ 145 | 2.310796, 146 | 48.7305252 147 | ], 148 | [ 149 | 2.3104424, 150 | 48.7303916 151 | ], 152 | [ 153 | 2.3101624, 154 | 48.7301875 155 | ] 156 | ] 157 | }, 158 | "properties": { 159 | "name": "Without inline style" 160 | } 161 | } 162 | ] 163 | } -------------------------------------------------------------------------------- /test/fixtures/missing_hr.gpx.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | { 4 | "geometry": { 5 | "coordinates": [ 6 | [ 7 | [ 8 | 13.8153798692, 9 | 47.8728961386, 10 | 434.94 11 | ], 12 | [ 13 | 13.815383641, 14 | 47.8728824761, 15 | 434.98 16 | ], 17 | [ 18 | 13.8153921068, 19 | 47.8728688974, 20 | 435.04 21 | ] 22 | ], 23 | [ 24 | [ 25 | 13.8153798692, 26 | 47.8728961386, 27 | 434.94 28 | ], 29 | [ 30 | 13.815383641, 31 | 47.8728824761, 32 | 434.98 33 | ], 34 | [ 35 | 13.8153921068, 36 | 47.8728688974, 37 | 435.04 38 | ], 39 | [ 40 | 13.8154002372, 41 | 47.8728551511, 42 | 435.11 43 | ], 44 | [ 45 | 13.8154059369, 46 | 47.8728421591, 47 | 435.22 48 | ], 49 | [ 50 | 13.815445248, 51 | 47.8727505449, 52 | 436.63 53 | ], 54 | [ 55 | 13.8154713996, 56 | 47.8726570029, 57 | 437.7 58 | ], 59 | [ 60 | 13.8155080285, 61 | 47.8725669812, 62 | 439.19 63 | ], 64 | [ 65 | 13.8155217748, 66 | 47.872460112, 67 | 440.16 68 | ], 69 | [ 70 | 13.8155432325, 71 | 47.8724118322, 72 | 440.7 73 | ] 74 | ], 75 | [ 76 | [ 77 | 13.8153798692, 78 | 47.8728961386, 79 | 434.94 80 | ], 81 | [ 82 | 13.815383641, 83 | 47.8728824761, 84 | 434.98 85 | ], 86 | [ 87 | 13.8153921068, 88 | 47.8728688974, 89 | 435.04 90 | ] 91 | ] 92 | ], 93 | "type": "MultiLineString" 94 | }, 95 | "properties": { 96 | "coordTimes": [ 97 | [ 98 | "2017-05-27T04:00:00Z", 99 | "2017-05-27T04:00:01Z", 100 | "2017-05-27T04:00:02Z" 101 | ], 102 | [ 103 | "2017-05-27T04:01:00Z", 104 | "2017-05-27T04:01:01Z", 105 | "2017-05-27T04:01:02Z", 106 | "2017-05-27T04:01:03Z", 107 | "2017-05-27T04:01:04Z", 108 | "2017-05-27T04:01:13Z", 109 | "2017-05-27T04:01:20Z", 110 | "2017-05-27T04:01:27Z", 111 | "2017-05-27T04:01:34Z", 112 | "2017-05-27T04:01:38Z" 113 | ], 114 | [ 115 | "2017-05-27T04:02:00Z", 116 | "2017-05-27T04:02:01Z", 117 | "2017-05-27T04:02:02Z" 118 | ] 119 | ], 120 | "heartRates": [ 121 | [ 122 | null, 123 | null, 124 | null 125 | ], 126 | [ 127 | 104, 128 | 104, 129 | 104, 130 | 105, 131 | 105, 132 | null, 133 | 107, 134 | 116, 135 | 120, 136 | 131 137 | ], 138 | [ 139 | null, 140 | null, 141 | null 142 | ] 143 | ], 144 | "name": "missing hr", 145 | "time": "2017-05-27T04:00:00Z" 146 | }, 147 | "type": "Feature" 148 | } 149 | ], 150 | "type": "FeatureCollection" 151 | } -------------------------------------------------------------------------------- /test/fixtures/multitrack.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | 3.682555, 10 | 51.050362, 11 | 50.599998474121094 12 | ] 13 | }, 14 | "properties": { 15 | "icon": "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png", 16 | "name": "8/8/2013 17:20 (Start)", 17 | "styleUrl": "#start", 18 | "styleHash": "5a145216", 19 | "timestamp": "2013-08-08T15:20:40.000Z" 20 | } 21 | }, 22 | { 23 | "type": "Feature", 24 | "geometry": { 25 | "type": "LineString", 26 | "coordinates": [ 27 | [ 28 | 3.68217, 29 | 51.050583, 30 | 54.599998474121094 31 | ], 32 | [ 33 | 3.68211, 34 | 51.050596, 35 | 54.79999923706055 36 | ], 37 | [ 38 | 3.68209, 39 | 51.050598, 40 | 55.400001525878906 41 | ], 42 | [ 43 | 3.682043, 44 | 51.050609, 45 | 55.70000076293945 46 | ], 47 | [ 48 | 3.682012, 49 | 51.050618, 50 | 54.5 51 | ], 52 | [ 53 | 3.68198, 54 | 51.050629, 55 | 54.29999923706055 56 | ], 57 | [ 58 | 3.681945, 59 | 51.050638, 60 | 54.79999923706055 61 | ], 62 | [ 63 | 3.681907, 64 | 51.050647, 65 | 54.900001525878906 66 | ], 67 | [ 68 | 3.68187, 69 | 51.050656, 70 | 54.599998474121094 71 | ], 72 | [ 73 | 3.681834, 74 | 51.050664, 75 | 54 76 | ], 77 | [ 78 | 3.681797, 79 | 51.050669, 80 | 55.29999923706055 81 | ], 82 | [ 83 | 3.681759, 84 | 51.050673, 85 | 55.400001525878906 86 | ], 87 | [ 88 | 3.681721, 89 | 51.050677, 90 | 55.20000076293945 91 | ], 92 | [ 93 | 3.681682, 94 | 51.050682, 95 | 54.70000076293945 96 | ], 97 | [ 98 | 3.681637, 99 | 51.050685, 100 | 55.20000076293945 101 | ], 102 | [ 103 | 3.681589, 104 | 51.050687, 105 | 55 106 | ] 107 | ] 108 | }, 109 | "properties": { 110 | "icon": "http://earth.google.com/images/kml-icons/track-directional/track-0.png", 111 | "name": "8/8/2013 17:20", 112 | "styleUrl": "#track", 113 | "styleHash": "66da7df6", 114 | "stroke": "#ff0000", 115 | "stroke-opacity": 0.4980392156862745, 116 | "stroke-width": 4, 117 | "type": "walking", 118 | "coordTimes": [ 119 | "2013-08-08T15:24:47.000Z", 120 | "2013-08-08T15:24:48.000Z", 121 | "2013-08-08T15:24:50.000Z", 122 | "2013-08-08T15:24:51.000Z", 123 | "2013-08-08T15:24:52.000Z", 124 | "2013-08-08T15:24:53.000Z", 125 | "2013-08-08T15:24:54.000Z", 126 | "2013-08-08T15:24:55.000Z", 127 | "2013-08-08T15:24:56.000Z", 128 | "2013-08-08T15:24:57.000Z", 129 | "2013-08-08T15:24:58.000Z", 130 | "2013-08-08T15:24:59.000Z", 131 | "2013-08-08T15:25:00.000Z", 132 | "2013-08-08T15:25:01.000Z", 133 | "2013-08-08T15:25:02.000Z", 134 | "2013-08-08T15:25:03.000Z" 135 | ] 136 | }, 137 | "id": "tour" 138 | }, 139 | { 140 | "type": "Feature", 141 | "geometry": { 142 | "type": "Point", 143 | "coordinates": [ 144 | 3.682725, 145 | 51.050615, 146 | 61.5 147 | ] 148 | }, 149 | "properties": { 150 | "icon": "http://maps.google.com/mapfiles/kml/paddle/red-circle.png", 151 | "name": "8/8/2013 17:20 (End)", 152 | "styleUrl": "#end", 153 | "styleHash": "-77e98803", 154 | "timestamp": "2013-08-08T16:25:57.000Z" 155 | } 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /test/fixtures/non_gx_multitrack.kml.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 9 | 3.682555, 10 | 51.050362, 11 | 50.599998474121094 12 | ] 13 | }, 14 | "properties": { 15 | "icon": "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png", 16 | "name": "8/8/2013 17:20 (Start)", 17 | "styleUrl": "#start", 18 | "styleHash": "5a145216", 19 | "timestamp": "2013-08-08T15:20:40.000Z" 20 | } 21 | }, 22 | { 23 | "type": "Feature", 24 | "geometry": { 25 | "type": "LineString", 26 | "coordinates": [ 27 | [ 28 | 3.68217, 29 | 51.050583, 30 | 54.599998474121094 31 | ], 32 | [ 33 | 3.68211, 34 | 51.050596, 35 | 54.79999923706055 36 | ], 37 | [ 38 | 3.68209, 39 | 51.050598, 40 | 55.400001525878906 41 | ], 42 | [ 43 | 3.682043, 44 | 51.050609, 45 | 55.70000076293945 46 | ], 47 | [ 48 | 3.682012, 49 | 51.050618, 50 | 54.5 51 | ], 52 | [ 53 | 3.68198, 54 | 51.050629, 55 | 54.29999923706055 56 | ], 57 | [ 58 | 3.681945, 59 | 51.050638, 60 | 54.79999923706055 61 | ], 62 | [ 63 | 3.681907, 64 | 51.050647, 65 | 54.900001525878906 66 | ], 67 | [ 68 | 3.68187, 69 | 51.050656, 70 | 54.599998474121094 71 | ], 72 | [ 73 | 3.681834, 74 | 51.050664, 75 | 54 76 | ], 77 | [ 78 | 3.681797, 79 | 51.050669, 80 | 55.29999923706055 81 | ], 82 | [ 83 | 3.681759, 84 | 51.050673, 85 | 55.400001525878906 86 | ], 87 | [ 88 | 3.681721, 89 | 51.050677, 90 | 55.20000076293945 91 | ], 92 | [ 93 | 3.681682, 94 | 51.050682, 95 | 54.70000076293945 96 | ], 97 | [ 98 | 3.681637, 99 | 51.050685, 100 | 55.20000076293945 101 | ], 102 | [ 103 | 3.681589, 104 | 51.050687, 105 | 55 106 | ] 107 | ] 108 | }, 109 | "properties": { 110 | "icon": "http://earth.google.com/images/kml-icons/track-directional/track-0.png", 111 | "name": "8/8/2013 17:20", 112 | "styleUrl": "#track", 113 | "styleHash": "66da7df6", 114 | "stroke": "#ff0000", 115 | "stroke-opacity": 0.4980392156862745, 116 | "stroke-width": 4, 117 | "type": "walking", 118 | "coordTimes": [ 119 | "2013-08-08T15:24:47.000Z", 120 | "2013-08-08T15:24:48.000Z", 121 | "2013-08-08T15:24:50.000Z", 122 | "2013-08-08T15:24:51.000Z", 123 | "2013-08-08T15:24:52.000Z", 124 | "2013-08-08T15:24:53.000Z", 125 | "2013-08-08T15:24:54.000Z", 126 | "2013-08-08T15:24:55.000Z", 127 | "2013-08-08T15:24:56.000Z", 128 | "2013-08-08T15:24:57.000Z", 129 | "2013-08-08T15:24:58.000Z", 130 | "2013-08-08T15:24:59.000Z", 131 | "2013-08-08T15:25:00.000Z", 132 | "2013-08-08T15:25:01.000Z", 133 | "2013-08-08T15:25:02.000Z", 134 | "2013-08-08T15:25:03.000Z" 135 | ] 136 | }, 137 | "id": "tour" 138 | }, 139 | { 140 | "type": "Feature", 141 | "geometry": { 142 | "type": "Point", 143 | "coordinates": [ 144 | 3.682725, 145 | 51.050615, 146 | 61.5 147 | ] 148 | }, 149 | "properties": { 150 | "icon": "http://maps.google.com/mapfiles/kml/paddle/red-circle.png", 151 | "name": "8/8/2013 17:20 (End)", 152 | "styleUrl": "#end", 153 | "styleHash": "-77e98803", 154 | "timestamp": "2013-08-08T16:25:57.000Z" 155 | } 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /src/services/math.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "geojson"; 2 | 3 | /** 4 | * Average radius of Earth in meters 5 | */ 6 | const EARTH_RADIUS = 6371008.8; 7 | 8 | /** 9 | * Average circumfrence of Earth in meters 10 | */ 11 | const EARTH_CIRCUMFERENCE = 2 * Math.PI * EARTH_RADIUS; 12 | 13 | /** 14 | * Convert a wgs84 longitude to web Mercator X (west-east axis), where westmost X is 0 and eastmost X is 1. 15 | */ 16 | function longitudeToMercatorX(lng: number): number { 17 | return (180 + lng) / 360; 18 | } 19 | 20 | /** 21 | * Convert a wgs84 latitude to web Mercator Y (north-south axis), where northmost Y is 0 and southmost Y is 1. 22 | */ 23 | function latitudeToMercatorY(lat: number): number { 24 | return ( 25 | (180 - 26 | (180 / Math.PI) * 27 | Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360))) / 28 | 360 29 | ); 30 | } 31 | 32 | /** 33 | * Convert a wgs84 position into a web Mercator position where north-west is [0, 0] and south-east is [1, 1] 34 | */ 35 | function wgs84ToMercator(position: Position): Position { 36 | const wrappedPos = wrapWgs84(position); 37 | return [ 38 | longitudeToMercatorX(wrappedPos[0]), 39 | latitudeToMercatorY(wrappedPos[1]), 40 | ]; 41 | } 42 | 43 | /** 44 | * Converts a mercator X (west-east axis in [0, 1]) to wgs84 longitude 45 | */ 46 | function mercatorXToLongitude(x: number): number { 47 | return x * 360 - 180; 48 | } 49 | 50 | /** 51 | * Converts a mercator Y (north-south axis in [0, 1]) to wgs84 latitude 52 | */ 53 | function mercatorYToLatitude(y: number): number { 54 | const y2 = 180 - y * 360; 55 | return (360 / Math.PI) * Math.atan(Math.exp((y2 * Math.PI) / 180)) - 90; 56 | } 57 | 58 | /** 59 | * Converts a web Mercator position where north-west is [0, 0] and south-east is [1, 1] into a wgs84 60 | */ 61 | function mercatorToWgs84(position: Position): Position { 62 | return [mercatorXToLongitude(position[0]), mercatorYToLatitude(position[1])]; 63 | } 64 | 65 | /** 66 | * Gives the distance in meters between two positions using the Haversine Formula. 67 | */ 68 | function haversineDistanceWgs84(from: Position, to: Position): number { 69 | const rad = Math.PI / 180; 70 | const lat1 = from[1] * rad; 71 | const lat2 = to[1] * rad; 72 | const a = 73 | Math.sin(lat1) * Math.sin(lat2) + 74 | Math.cos(lat1) * Math.cos(lat2) * Math.cos((to[0] - from[0]) * rad); 75 | 76 | const maxMeters = EARTH_RADIUS * Math.acos(Math.min(a, 1)); 77 | return maxMeters; 78 | } 79 | 80 | /** 81 | * Compute the cumulated distance for each position of an array of positions. 82 | * For I positions, there are I-1 distance, hence the distance at i corresponds 83 | * to the distance from the (i-1)th position to ith 84 | */ 85 | function haversineCumulatedDistanceWgs84(positions: Position[]): number[] { 86 | const cumulatedDistance = Array(positions.length); 87 | cumulatedDistance[0] = 0; 88 | const l = cumulatedDistance.length; 89 | 90 | for (let i = 1; i < l; i++) { 91 | cumulatedDistance[i] = 92 | haversineDistanceWgs84(positions[i - 1], positions[i]) + 93 | cumulatedDistance[i - 1]; 94 | } 95 | return cumulatedDistance; 96 | } 97 | 98 | /** 99 | * Returns a position that has longitude in [-180, 180] 100 | */ 101 | function wrapWgs84(position: Position): Position { 102 | const lng = position[0]; 103 | const lat = position[1]; 104 | 105 | const d = 360; 106 | const w = ((((lng + 180) % d) + d) % d) - 180; 107 | const wrapLong = w === -180 ? 180 : w; 108 | 109 | return [wrapLong, lat]; 110 | } 111 | 112 | /* 113 | * The circumference at a line of latitude in meters. 114 | */ 115 | export function circumferenceAtLatitude(latitude: number) { 116 | return EARTH_CIRCUMFERENCE * Math.cos((latitude * Math.PI) / 180); 117 | } 118 | 119 | /** 120 | * From a given mercator coordinate and a zoom level, computes the tile index 121 | */ 122 | function mercatorToTileIndex( 123 | /** 124 | * Mercator coordinates (north-west is [0, 0], sourth-east is [1, 1]) 125 | */ 126 | position: Position, 127 | /** 128 | * Zoom level 129 | */ 130 | zoom: number, 131 | /** 132 | * Returns integer tile indices if `true` or floating-point values if `false` 133 | */ 134 | strict: boolean = true, 135 | ): Position { 136 | const numberOfTilePerAxis = 2 ** zoom; 137 | 138 | const fIndex: Position = [ 139 | position[0] * numberOfTilePerAxis, 140 | position[1] * numberOfTilePerAxis, 141 | ]; 142 | 143 | return strict ? [~~fIndex[0], ~~fIndex[1]] : fIndex; 144 | } 145 | 146 | /** 147 | * From a given wgs84 coordinate and a zoom level, computes the tile index 148 | */ 149 | function wgs84ToTileIndex( 150 | /** 151 | * Wgs84 coordinates 152 | */ 153 | position: Position, 154 | /** 155 | * Zoom level 156 | */ 157 | zoom: number, 158 | /** 159 | * Returns integer tile indices if `true` or floating-point values if `false` 160 | */ 161 | strict: boolean = true, 162 | ): Position { 163 | const merc = wgs84ToMercator(position); 164 | return mercatorToTileIndex(merc, zoom, strict); 165 | } 166 | 167 | /** 168 | * Converts a degree angle into a radian angle 169 | */ 170 | function toRadians(degrees: number): number { 171 | return (degrees * Math.PI) / 180; 172 | } 173 | 174 | /** 175 | * Converts a radian angle to a degree angle 176 | */ 177 | function toDegrees(radians: number): number { 178 | return (radians * 180) / Math.PI; 179 | } 180 | 181 | /** 182 | * Compute an intermediate point between two reference points using the Haversine formula. 183 | * If ratio is `0`, the returned position is pos1. 184 | * If ratio is `1`, the returned position is pos2. 185 | * If ratio is `0.5`, the returned position is halfway pos1 pos2 in distance. 186 | */ 187 | function haversineIntermediateWgs84( 188 | pos1: Position, 189 | pos2: Position, 190 | ratio: number, 191 | ): Position { 192 | const d = haversineDistanceWgs84(pos1, pos2); 193 | const λ1 = toRadians(pos1[0]); 194 | const φ1 = toRadians(pos1[1]); 195 | const λ2 = toRadians(pos2[0]); 196 | const φ2 = toRadians(pos2[1]); 197 | 198 | const δ = d / EARTH_RADIUS; // Angular distance in radians 199 | const a = Math.sin((1 - ratio) * δ) / Math.sin(δ); 200 | const b = Math.sin(ratio * δ) / Math.sin(δ); 201 | const x = a * Math.cos(φ1) * Math.cos(λ1) + b * Math.cos(φ2) * Math.cos(λ2); 202 | const y = a * Math.cos(φ1) * Math.sin(λ1) + b * Math.cos(φ2) * Math.sin(λ2); 203 | const z = a * Math.sin(φ1) + b * Math.sin(φ2); 204 | 205 | const φ3 = Math.atan2(z, Math.sqrt(x * x + y * y)); 206 | const λ3 = Math.atan2(y, x); 207 | 208 | return [toDegrees(λ3), toDegrees(φ3)]; 209 | } 210 | 211 | export const math = { 212 | EARTH_RADIUS, 213 | EARTH_CIRCUMFERENCE, 214 | longitudeToMercatorX, 215 | latitudeToMercatorY, 216 | wgs84ToMercator, 217 | mercatorXToLongitude, 218 | mercatorYToLatitude, 219 | mercatorToWgs84, 220 | haversineDistanceWgs84, 221 | wrapWgs84, 222 | circumferenceAtLatitude, 223 | mercatorToTileIndex, 224 | wgs84ToTileIndex, 225 | toRadians, 226 | toDegrees, 227 | haversineIntermediateWgs84, 228 | haversineCumulatedDistanceWgs84, 229 | }; 230 | -------------------------------------------------------------------------------- /src/services/coordinates.ts: -------------------------------------------------------------------------------- 1 | import { BBox, Position } from "geojson"; 2 | import { callFetch } from "../callFetch"; 3 | import { config } from "../config"; 4 | import { defaults } from "../defaults"; 5 | import { ServiceError } from "./ServiceError"; 6 | 7 | const customMessages = { 8 | 403: "Key is missing, invalid or restricted", 9 | }; 10 | 11 | export type CoordinatesSearchOptions = { 12 | /** 13 | * Custom MapTiler Cloud API key to use instead of the one in global `config` 14 | */ 15 | apiKey?: string; 16 | 17 | /** 18 | * Maximum number of results returned (default: 10) 19 | */ 20 | limit?: number; 21 | 22 | /** 23 | * Show detailed transformations for each CRS (default: false) 24 | */ 25 | transformations?: boolean; 26 | 27 | /** 28 | * Show exports in WKT and Proj4 notations (default: false) 29 | */ 30 | exports?: boolean; 31 | }; 32 | 33 | export type CoordinateId = { 34 | authority: string; 35 | code: BigInteger; 36 | }; 37 | 38 | export type CoordinateExport = { 39 | proj4: string; 40 | wkt: string; 41 | }; 42 | 43 | export type CoordinateGrid = { 44 | path: string; 45 | }; 46 | 47 | export type CoordinateTransformation = { 48 | id: CoordinateId; 49 | name: string; 50 | reversible: boolean; 51 | usable: boolean; 52 | deprecated: boolean; 53 | grids: Array; 54 | accuracy?: number; 55 | area?: string; 56 | bbox?: BBox; 57 | target_crs?: CoordinateId; 58 | unit?: string; 59 | }; 60 | 61 | export type CoordinateSearch = { 62 | id: CoordinateId; 63 | 64 | name: string; 65 | 66 | kind: string; 67 | 68 | deprecated: boolean; 69 | 70 | transformations?: Array; 71 | 72 | accuracy?: number; 73 | 74 | unit?: string; 75 | 76 | area?: string; 77 | 78 | /** 79 | * Bounding box of the resource in [min_lon, min_lat, max_lon, max_lat] order. 80 | */ 81 | bbox?: BBox; 82 | 83 | /** 84 | * Most suitable transformation for this CRS. 85 | */ 86 | default_transformation?: DefaultTransformation; 87 | 88 | exports: CoordinateExport; 89 | }; 90 | 91 | export type DefaultTransformation = { 92 | authority: string; 93 | code: number; 94 | }; 95 | 96 | export type CoordinateSearchResult = { 97 | /** 98 | * The coordinate search results 99 | */ 100 | results: Array; 101 | 102 | /** 103 | * The number of results 104 | */ 105 | total: number; 106 | }; 107 | 108 | /** 109 | * Search information about coordinate systems using MapTiler API. 110 | * Learn more on the MapTiler API reference page: https://docs.maptiler.com/cloud/api/coordinates/#search-coordinate-systems 111 | * @param query Can be any kind of CRS by name or code 112 | * @param options 113 | * @returns 114 | */ 115 | async function search( 116 | query: string, 117 | options: CoordinatesSearchOptions = {}, 118 | ): Promise { 119 | if (typeof query !== "string" || query.trim().length === 0) { 120 | throw new Error("The query must be a non-empty string"); 121 | } 122 | 123 | const endpoint = new URL( 124 | `coordinates/search/${query}.json`, 125 | defaults.maptilerApiURL, 126 | ); 127 | endpoint.searchParams.set("key", options.apiKey ?? config.apiKey); 128 | 129 | if ("limit" in options) { 130 | endpoint.searchParams.set("limit", options.limit.toString()); 131 | } 132 | 133 | if ("transformations" in options) { 134 | endpoint.searchParams.set( 135 | "transformations", 136 | options.transformations.toString(), 137 | ); 138 | } 139 | 140 | if ("exports" in options) { 141 | endpoint.searchParams.set("exports", options.exports.toString()); 142 | } 143 | 144 | const urlWithParams = endpoint.toString(); 145 | const res = await callFetch(urlWithParams); 146 | 147 | if (!res.ok) { 148 | throw new ServiceError( 149 | res, 150 | res.status in customMessages ? customMessages[res.status] : "", 151 | ); 152 | } 153 | 154 | const obj = await res.json(); 155 | return obj as CoordinateSearchResult; 156 | } 157 | 158 | export type XYZ = { 159 | x?: number; 160 | y?: number; 161 | z?: number; 162 | }; 163 | 164 | export type CoordinateTransformResult = { 165 | results: Array; 166 | 167 | /** 168 | * Transformations are selected using given ops parameter. 169 | * If no parameter is given, auto strategy is used. 170 | * If given, it may try to use a listed transformation, 171 | * then fallback to towgs84 patching, and finally boundcrs. 172 | */ 173 | transformer_selection_strategy: string; 174 | }; 175 | 176 | /** 177 | * Options that can be provided when transforming a coordinate from one CRS to another. 178 | */ 179 | export type CoordinatesTransformOptions = { 180 | /** 181 | * Custom MapTiler Cloud API key to use instead of the one in global `config` 182 | */ 183 | apiKey?: string; 184 | 185 | /** 186 | * Source coordinate reference system (default: 4326) 187 | */ 188 | sourceCrs?: number; 189 | 190 | /** 191 | * Target coordinate reference system (default: 4326) 192 | */ 193 | targetCrs?: number; 194 | 195 | /** 196 | * List of codes of operations 197 | */ 198 | operations?: number | Array; 199 | }; 200 | 201 | /** 202 | * Transforms coordinates from a source reference system to a target reference system using MapTiler API. 203 | * Learn more on the MapTiler API reference page: https://docs.maptiler.com/cloud/api/coordinates/#transform-coordinates 204 | * @param positions 205 | * @param options 206 | * @returns 207 | */ 208 | async function transform( 209 | positions: Position | Array, 210 | options: CoordinatesTransformOptions = {}, 211 | ): Promise { 212 | const coordinatesStr = (Array.isArray(positions[0]) ? positions : [positions]) 213 | .map((coord) => `${coord[0]},${coord[1]}`) 214 | .join(";"); 215 | 216 | const endpoint = new URL( 217 | `coordinates/transform/${coordinatesStr}.json`, 218 | defaults.maptilerApiURL, 219 | ); 220 | endpoint.searchParams.set("key", options.apiKey ?? config.apiKey); 221 | 222 | if ("sourceCrs" in options) { 223 | endpoint.searchParams.set("s_srs", options.sourceCrs.toString()); 224 | } 225 | 226 | if ("targetCrs" in options) { 227 | endpoint.searchParams.set("t_srs", options.targetCrs.toString()); 228 | } 229 | 230 | if ("operations" in options) { 231 | endpoint.searchParams.set( 232 | "ops", 233 | (Array.isArray(options.operations) 234 | ? options.operations 235 | : [options.operations] 236 | ).join("|"), 237 | ); 238 | } 239 | 240 | const urlWithParams = endpoint.toString(); 241 | const res = await callFetch(urlWithParams); 242 | 243 | if (!res.ok) { 244 | throw new ServiceError( 245 | res, 246 | res.status in customMessages ? customMessages[res.status] : "", 247 | ); 248 | } 249 | 250 | const obj = await res.json(); 251 | return obj as CoordinateTransformResult; 252 | } 253 | 254 | /** 255 | * The **coordinate** namespace contains asynchronous functions to call the [MapTiler Coordinate API](https://docs.maptiler.com/cloud/api/coordinates/). 256 | * The goal of the **Coordinate API* is query information about spatial coordinate reference system (CRS) as well as to transform coordinates from one CRS to another. 257 | */ 258 | const coordinates = { 259 | search, 260 | transform, 261 | }; 262 | 263 | export { coordinates }; 264 | -------------------------------------------------------------------------------- /test/fixtures/gxmultitrack.kml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 1 7 | 1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 28 | 37 | 46 | 55 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 2014-04-12T14:26:16.702Z 90 | 91 | #start 92 | 93 | -71.324739,-40.139527,791.5999755859375 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | #track 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | absolute 113 | 1 114 | 115 | 2014-04-12T14:26:16.702Z 116 | -71.324739 -40.139527 791.5999755859375 117 | 2014-04-12T14:26:52.761Z 118 | -71.324819 -40.139536 772.0 119 | 2014-04-12T14:26:53.745Z 120 | -71.324824 -40.139537 771.800048828125 121 | 2014-04-12T14:26:56.656Z 122 | -71.324828 -40.139538 771.699951171875 123 | 2014-04-12T14:26:57.639Z 124 | -71.324839 -40.139552 772.5999755859375 125 | 2014-04-12T14:26:58.658Z 126 | -71.324839 -40.139556 773.0 127 | 2014-04-12T14:26:59.744Z 128 | -71.324843 -40.139555 773.199951171875 129 | 2014-04-12T14:27:16.749Z 130 | -71.324848 -40.139563 774.0 131 | 2014-04-12T14:27:17.741Z 132 | -71.324879 -40.139559 774.300048828125 133 | 2014-04-12T14:27:19.657Z 134 | -71.324871 -40.139579 774.5999755859375 135 | 2014-04-12T14:27:20.643Z 136 | -71.32487 -40.139578 774.699951171875 137 | 2014-04-12T14:27:23.687Z 138 | -71.324874 -40.139583 774.5 139 | 2014-04-12T14:27:24.638Z 140 | -71.324879 -40.139577 774.4000244140625 141 | 142 | 143 | 144 | 145 | 146 | 2014-04-12T14:26:16.702Z 147 | -72.324739 -40.139527 791.5999755859375 148 | 2014-04-12T14:26:52.761Z 149 | -72.324819 -40.139536 772.0 150 | 2014-04-12T14:26:53.745Z 151 | -72.324824 -40.139537 771.800048828125 152 | 2014-04-12T14:26:56.656Z 153 | -72.324828 -40.139538 771.699951171875 154 | 2014-04-12T14:26:57.639Z 155 | -72.324839 -40.139552 772.5999755859375 156 | 2014-04-12T14:26:58.658Z 157 | -71.324839 -40.139556 773.0 158 | 2014-04-12T14:26:59.744Z 159 | -71.324843 -40.139555 773.199951171875 160 | 2014-04-12T14:27:16.749Z 161 | -71.324848 -40.139563 774.0 162 | 2014-04-12T14:27:17.741Z 163 | -71.324879 -40.139559 774.300048828125 164 | 2014-04-12T14:27:19.657Z 165 | -71.324871 -40.139579 774.5999755859375 166 | 2014-04-12T14:27:20.643Z 167 | -71.32487 -40.139578 774.699951171875 168 | 2014-04-12T14:27:23.687Z 169 | -71.324874 -40.139583 774.5 170 | 2014-04-12T14:27:24.638Z 171 | -71.324879 -40.139577 774.4000244140625 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 204 | 205 | 206 | 2014-04-12T15:38:07.678Z 207 | 208 | #end 209 | 210 | -71.353544,-40.157777,677.300048828125 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /test/fixtures/north_core.gpx.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "type": "NC" 8 | }, 9 | "geometry": { 10 | "type": "LineString", 11 | "coordinates": [ 12 | [ 13 | 2.665278, 14 | 10.228889 15 | ], 16 | [ 17 | 2.933333, 18 | 11.133333 19 | ] 20 | ] 21 | } 22 | }, 23 | { 24 | "type": "Feature", 25 | "properties": { 26 | "type": "NC" 27 | }, 28 | "geometry": { 29 | "type": "LineString", 30 | "coordinates": [ 31 | [ 32 | 2.933333, 33 | 11.133333 34 | ], 35 | [ 36 | 3.435664, 37 | 11.862522 38 | ] 39 | ] 40 | } 41 | }, 42 | { 43 | "type": "Feature", 44 | "properties": { 45 | "type": "NC" 46 | }, 47 | "geometry": { 48 | "type": "MultiLineString", 49 | "coordinates": [ 50 | [ 51 | [ 52 | 0.99212, 53 | 13.164212 54 | ], 55 | [ 56 | -0.517094, 57 | 12.688132 58 | ] 59 | ], 60 | [ 61 | [ 62 | -0.517094, 63 | 12.688132 64 | ], 65 | [ 66 | -0.667626, 67 | 12.640647 68 | ] 69 | ], 70 | [ 71 | [ 72 | -0.667626, 73 | 12.640647 74 | ], 75 | [ 76 | -1.524722, 77 | 12.370277 78 | ] 79 | ] 80 | ] 81 | } 82 | }, 83 | { 84 | "type": "Feature", 85 | "properties": { 86 | "type": "NC" 87 | }, 88 | "geometry": { 89 | "type": "LineString", 90 | "coordinates": [ 91 | [ 92 | 3.816438, 93 | 12.667497 94 | ], 95 | [ 96 | 4.184197, 97 | 12.446262 98 | ], 99 | [ 100 | 4.196806, 101 | 12.450347 102 | ] 103 | ] 104 | } 105 | }, 106 | { 107 | "type": "Feature", 108 | "properties": { 109 | "type": "NC" 110 | }, 111 | "geometry": { 112 | "type": "LineString", 113 | "coordinates": [ 114 | [ 115 | 3.816435, 116 | 12.667498 117 | ], 118 | [ 119 | 3.816438, 120 | 12.667497 121 | ] 122 | ] 123 | } 124 | }, 125 | { 126 | "type": "Feature", 127 | "properties": { 128 | "type": "NC" 129 | }, 130 | "geometry": { 131 | "type": "MultiLineString", 132 | "coordinates": [ 133 | [ 134 | [ 135 | 3.2, 136 | 13.05 137 | ], 138 | [ 139 | 3.198604, 140 | 13.03917 141 | ], 142 | [ 143 | 3.572865, 144 | 12.814024 145 | ] 146 | ], 147 | [ 148 | [ 149 | 3.572865, 150 | 12.814024 151 | ], 152 | [ 153 | 3.816435, 154 | 12.667498 155 | ] 156 | ] 157 | ] 158 | } 159 | }, 160 | { 161 | "type": "Feature", 162 | "properties": { 163 | "type": "NC" 164 | }, 165 | "geometry": { 166 | "type": "LineString", 167 | "coordinates": [ 168 | [ 169 | 2.12, 170 | 13.52 171 | ], 172 | [ 173 | 2.124997, 174 | 13.50692 175 | ], 176 | [ 177 | 3.191601, 178 | 13.042749 179 | ], 180 | [ 181 | 3.2, 182 | 13.05 183 | ] 184 | ] 185 | } 186 | }, 187 | { 188 | "type": "Feature", 189 | "properties": { 190 | "type": "NC" 191 | }, 192 | "geometry": { 193 | "type": "MultiLineString", 194 | "coordinates": [ 195 | [ 196 | [ 197 | 2.119999, 198 | 13.52 199 | ], 200 | [ 201 | 0.99212, 202 | 13.164212 203 | ] 204 | ], 205 | [ 206 | [ 207 | 2.12, 208 | 13.52 209 | ], 210 | [ 211 | 2.119999, 212 | 13.52 213 | ] 214 | ] 215 | ] 216 | } 217 | }, 218 | { 219 | "type": "Feature", 220 | "properties": { 221 | "type": "NC" 222 | }, 223 | "geometry": { 224 | "type": "LineString", 225 | "coordinates": [ 226 | [ 227 | 3.435664, 228 | 11.862522 229 | ], 230 | [ 231 | 3.45, 232 | 11.883333 233 | ], 234 | [ 235 | 3.572865, 236 | 12.814024 237 | ] 238 | ] 239 | } 240 | }, 241 | { 242 | "type": "Feature", 243 | "properties": { 244 | "type": "NC" 245 | }, 246 | "geometry": { 247 | "type": "LineString", 248 | "coordinates": [ 249 | [ 250 | 3.816435, 251 | 12.667498 252 | ], 253 | [ 254 | 3.816438, 255 | 12.667497 256 | ] 257 | ] 258 | } 259 | } 260 | ] 261 | } -------------------------------------------------------------------------------- /test/fixtures/addresses.kml: -------------------------------------------------------------------------------- 1 | Location history from 2015-08-01 to 2015-08-08 1normal#multiTrack_nhighlight#multiTrack_hIn transit
In transit0 In transit from 2015-07-31T17:00:00.000Z to 2015-08-01T00:00:00.000Z. Distance 0m clampToGround2015-07-31T17:00:00.000Z2015-08-01T00:00:00.000Z
Moving
Moving173 Moving from 2015-08-01T00:00:00.000Z to 2015-08-01T09:04:32.323Z. Distance 173m clampToGround100.5513698 13.7987389 0100.5515619 13.7992727 02015-08-01T00:00:00.000Z2015-08-01T09:04:32.323Z
Walking
Walking0 Walking from 2015-08-01T09:04:32.323Z to 2015-08-01T09:16:56.610Z. Distance 0m clampToGround2015-08-01T09:04:32.323Z2015-08-01T09:16:56.610Z
On the subway
On the subway4470 On the subway from 2015-08-01T09:16:56.610Z to 2015-08-01T09:43:53.321Z. Distance 4470m clampToGround100.5343967 13.745564 02015-08-01T09:16:56.610Z2015-08-01T09:43:53.321Z
Lub d - Bangkok Siam
925/9 Rama 1 Rd. Wangmai, Pathum Wan, Bangkok 10330, Thailand
Budget Hotel0 Budget Hotel from 2015-08-01T09:43:58.715Z to 2015-08-01T16:31:30.753Z. Distance 0m clampToGround100.52868400000001 13.746737 02015-08-01T09:43:58.715Z2015-08-01T16:31:30.753Z
In transit
In transit0 In transit from 2015-08-01T16:31:30.753Z to 2015-08-01T16:55:33.088Z. Distance 0m clampToGround100.52868400000001 13.746737 02015-08-01T16:31:30.753Z2015-08-01T16:55:33.088Z
Bang Kapi
Huai Khwang, Bangkok, Thailand
Sublocality20 Sublocality2 from 2015-08-01T16:55:39.324Z to 2015-08-01T17:13:08.282Z. Distance 0m clampToGround100.59150720000001 13.749175999999999 02015-08-01T16:55:39.324Z2015-08-01T17:13:08.282Z
Driving
Driving0 Driving from 2015-08-01T17:13:08.282Z to 2015-08-01T17:34:21.480Z. Distance 0m clampToGround100.57488623703713 13.752278881572217 02015-08-01T17:13:08.282Z2015-08-01T17:34:21.480Z
Walking
Walking0 Walking from 2015-08-01T17:34:21.480Z to 2015-08-01T17:49:42.729Z. Distance 0m clampToGround2015-08-01T17:34:21.480Z2015-08-01T17:49:42.729Z
Driving
Driving0 Driving from 2015-08-01T17:49:42.729Z to 2015-08-01T18:10:35.378Z. Distance 0m clampToGround2015-08-01T17:49:42.729Z2015-08-01T18:10:35.378Z
Walking
Walking0 Walking from 2015-08-01T18:10:35.378Z to 2015-08-01T18:58:19.307Z. Distance 0m clampToGround2015-08-01T18:10:35.378Z2015-08-01T18:58:19.307Z
Driving
Driving0 Driving from 2015-08-01T18:58:19.307Z to 2015-08-01T19:16:48.446Z. Distance 0m clampToGround2015-08-01T18:58:19.307Z2015-08-01T19:16:48.446Z
Walking
Walking0 Walking from 2015-08-01T19:16:48.446Z to 2015-08-01T19:29:38.452Z. Distance 0m clampToGround2015-08-01T19:16:48.446Z2015-08-01T19:29:38.452Z
Driving
Driving885 Driving from 2015-08-01T19:29:38.452Z to 2015-08-01T19:35:18.504Z. Distance 885m clampToGround100.5481932 13.7562302 0100.5403726 13.7563681 02015-08-01T19:29:38.452Z2015-08-01T19:35:18.504Z
Walking
Walking0 Walking from 2015-08-01T19:35:18.504Z to 2015-08-01T19:39:28.251Z. Distance 0m clampToGround2015-08-01T19:35:18.504Z2015-08-01T19:39:28.251Z
--------------------------------------------------------------------------------